乐趣区

关于class:我所知道JVM虚拟机之Class文件结构一描述介绍

前言

后面泛滥文章无关解说的都是些 JVM 的内存与垃圾回收器相干信息,那么对于本篇开始咱们将把眼光转移到 Class 文件与加载器身上去,去看看字节码文件里到底有些什么信息?是怎么加载到咱们内存里?

一、Class 文件的概述


字节码文件的跨平台性

================================

Java 语言:跨平台性(write one run anywhere)

当 Java 源代码胜利编译成字节码后,如果想在不同的平台下面运行,则毋庸再次编译

这个劣势不再那么吸引人了。Python、PHP、Per1、Ruby、Lisp 等有弱小的解释器

跨平台仿佛曾经快成为一门语言必选的个性

java 虚拟机:跨语言的平台

Java 虚拟机不和包含 Java 在内的任何语言绑定,它只与“Class 文件”这种特定的二进制文件格式所关联。

无论应用何种语言进行软件开发,只有能将源文件编译为正确的 Class 文件,那么这种语言就能够在 Java 虚拟机上执行。能够说,对立而弱小的 Class 文件构造,就是 Java 虚拟机的基石、桥梁。

JAVA 语言和 JVM 的标准

可拜访官网入口查看具体的标准:拜访地址

恪守 Java 虚拟机标准,也就是说所有的 JVM 环境都是一样的,这样一来字节码文件能够在各种 Jw 上运行。

Java 的前端编译器

================================

JAVA 源代码遵循 JVM 标准可失常运行在 JVM 中

前端编译器的次要工作就是负责将 合乎 Java 语法标准的 Java 代码转换为合乎 JVM 标准的字节码文件

javac 是一种可能将 Java 源码编译为 字节码的前端编译器

Javac 编译器在将 Java 源码编译为一个无效的字节码文件过程中经验了 4 个步骤别离是

  • 词法解析
  • 语法解析
  • 语义解析
  • 生成字节码

前端编译器 VS 后端编译器

HotSpot VN 并没有强制要求前端编译器 只能应用 javac 来编译字节码,其实只有编译后果合乎 JVW 标准都能够被 JVM 所辨认即可

在 Java 的前端编译器畛域 除了 javac 之外 ,还有一种被大家常常用到的前端编译器,那就是 内置在 Eclipse 中的 ECJ(EclipseCompiler for Java)编译器

和 Javac 的全量式编译不同,EC 是一种增量式编译器

在 Eclipse 中,当开发人员编写完代码后,应用“Ctrl+S”快捷键时,ECJ 编译器所采取的编译计划是 把未编译局部的源码逐行进行编译,而非每次都全量编译

因而 ECJ 的编译效率会比 javac 更加迅速和高效,当然编译品质和 javac 相比大抵还是一样的

EC 不仅是 Eclipse 的默认内置前端编译器,在 Tomcat 中同样也是应用 ECJ 编译器来编译 jsp 文件

因为 ECJ 编译器是采纳 GPLv2 的开源协定进行源代码公开,所以大家能够登录 eclipse 官网下载 ECJ 编译器的源码进行二次开发。

默认状况下,IntelliJ IDEA 应用 javac 编译器。(还能够本人设置为 Aspect]编译器 ajc)

前端编译器并不会间接波及编译优化等方面的技术,而是将这些具体优化细节移交给 HotSpot 的 3IT 编译器负责

透过字节码指令看代码细节

================================

先来看看几个常见的面试题

  • 类文件构造分几个局部?
  • 字节码都有哪些?Integer x = 5,int y = 5 比拟 x == y 都通过哪些步骤

在面对这些问题的时候,接下来先看上面这个示例代码是怎么回事

public class IntegerTesti
    
    public static void main(String[] args) {
           
        Integer x = 5;
        int y = 5;
        
        System.out.print1n(x == y);   
           
        Integer i1 = 10;
        Integer i2 = 10;
        
        System.out.print1n(i1 == i2);
        
        Integer i3 = 128;
        Integer i4 = 128;
        System.out.println(i3 == i4);
    }
}

依照咱们后面的常识,咱们能够回顾一下看看操作数栈与局部变量表里是怎么样操作的?

接下来咱们能够在应用一个示例代码来领会这种过程

public class stringTest {public static void main( string[] args) i
        string str = new string("hello") + new String("world");
        String str1 ="hellowor1d";
        system.out.println(str == str1);    
    }
}

咱们回顾回顾后面知识点,看看第一行代码 + 符号做了些什么事件

接下来咱们能够在应用一个示例代码来领会这种过程

class Father{

    int x = 10;
    
    public Father(){this.print();
        x = 20;
    }
    
    public void print() {system.out.println( "Father.x =" + x);
    }
}
class son extends Father{

    int x = 30;
    
    public son(){this.print();
       x = 40;
    }
    
    // 重写父类的办法
    public void print(){system.out.print1n( "Son.x =”+x);
    }
}

此时咱们用一个 Test 类调用这里两个类,看看将程序运行后会输入什么呢?

public class SonTest{public static void main(String[] args) {Father f = new Son();
        system.out.println(f.x);
    }
}
// 运行后果如下:Son.x = 0; 
Son.x = 30; 
20

这时会有小伙伴好奇了,为什么会是这样的一个输入后果呢?咱们先看简略的是什么状况

public class SonTest{public static void main(String[] args) {Father f = new Father();
        system.out.println(f.x);
    }
}
// 运行后果如下:Father.x = 10
20

对于非动态的成员变量的赋值过程次要分为以下几种:

  • 默认初始化
  • 显示初始化 / 代码块初始化
  • 结构器中初始化
  • 有对象后,可通过对象. 属性名调用(权限容许状况下)

接下来咱们通过字节码指令的形式看看是不是与咱们说的步骤是差不多的?

此时咱们回到刚刚 Test 调用 Son 类的状况,进行剖析看看

public class SonTest{public static void main(String[] args) {Father f = new Son();
        system.out.println(f.x);
    }
}
// 运行后果如下:Son.x = 0; 
Son.x = 30; 
20

接下来咱们通过字节码指令的形式看看 Son 做了哪些事件

之前咱们剖析了 Father 的结构器办法有哪些,以及做了哪些事件

此时调用 Father 的结构器时,应该执行 print 办法,然而 Son 类重写了所以调用 Son 类的办法

二、虚拟机的基石:CLass 文件


字节码文件里是什么?

================================

源代码通过编译器编译之后便会生成一个字节码文件,字节码是 一种二进制的类文件,它的内容是 jVM 的指令 ,而不像 C、C++ 经由编译器间接生成 机器码

什么是字节码指令(byte code)?

================================

JAVA 虚拟机的指令由 一个字节长度的、代表着某种特定操作含意的操作码(opcode)以及追随其后的零至多个代表此操作所需参数的操作数(operand)所形成

虚拟机中许多指令并不蕴含操作数,只有一个操作码,比如说咱们能够看看下面示例代码的

如何解读供虚拟机解释执行的二进制字节码

================================

形式一:采纳 notepad++,装置 HEX-Edirot 插件或者 Binary Viewer 插件

形式二:IDEA 插件: jclasslib 或 jclasslib bytecode viewer 客户端工具

形式三:应用 Javap 指令:jdk 自带反解析工具

三、Class 文件的构造


官网文档地位

================================

可拜访官网入口查看具体的内容:拜访地址

Class 类的实质

================================

任何一个 Class 文件都对应着惟一一个类或接口的定义信息,但反过来说,Class 文件实际上它并不一定以磁盘文件的模式存在(可网络传)。Class 实质是一组以 8 位字节为根底单位的二进制流。

Class 文件格式

================================

Class 的构造不像 XML 等描述语言,因为它没有任何分隔符号。

所以在其中的数据项,无论是字节程序还是数量,都是被严格限定的,哪个字节代表什么含意,长度是多少,先后顺序如何,都不容许扭转。

文件格式采纳一种相似于 c 语言构造体的形式进行数据存储,这种构造中只有:无符号数和表

  • 无符号数属于根本的数据类型,以 u1、u2、u4、u8 来别离代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数,无符号数能够用来形容数字、索引援用、数量值或者依照 UTF- 8 编码形成字符串值。
  • 表是由多个无符号数或者其余表作为数据项形成的复合数据类型,所有表都习惯性地以“_info”结尾。表用于 形容有档次关系的复合构造的数据,整个 Class 文件实质上就是一张表。因为表没有固定长度,所以通常会在其后面加上个数阐明

Class 文件构造

================================

Class 文件的构造并不是变化无穷的,随着 Java 虚拟机的一直倒退,总是不可避免地会对 Class 文件构造做出一些调整,然而其根本构造和框架是十分稳固的。

Class 文件的总体构造如下:
  • 魔数
  • class 文件版本常量池
  • 拜访标记
  • 类索引,父类索引,接口索引汇合
  • 字段表汇合
  • 办法表汇合
  • 属性表汇合

魔数构造解说:

用四个字节来示意魔数,次要对应辨认以后文件是不是一个 Class 文件的标识

Class 文件版本构造解说:

以后字节码文件是在那个版本下运行的编译,分为大版本 / 小版本(主版本 / 副版本)

常量池构造解说:

通过两个字节通知我常量池多长,上面是该常量池的数组

拜访标识标识解说:

用于以后标识是否为一个类还是接口、权限是什么?有没有 abstract 润饰、final 润饰等

类索引、父类索引、接口索引汇合解说:

用于指明以后类是什么名、父类是什么名、以后类实现的接口长度、寄存的数组等信息

字段表汇合解说:

一个类可有多个字段、所以这里也要表明长度、以及寄存字段的数组信息

办法表汇合解说:

以后类定义的办法,也有一个办法表的长度、以及寄存办法的数组信息

属性表汇合解说:

咱们进行一个类的字节码的时候,个别办法里 Code 这些属性,就指的这

退出移动版