深入理解jvm-2Edition-类文件结构

概述:

  规范而独立的类文件结构以及只与类文件关联的虚拟机为Java实现了平台无关性,甚至还带来了一些语言无关性

  只要将源代码编译为Class文件规定的格式,JVM就可以执行。

  JVM的指令描述能力比Java更强,这使得JVM可以执行不同于Java语言特性的语言。

1、Class文件整体结构

  

  以字节为基本单位无分隔符大端(低地址存高位)。

  无论是数量还是顺序都严格规定了(确定性)。

  两种数据类型:无符号数

    1、无符号数:u1、u2、、u4、u8分别代表1、2、4、8字节的无符号数。可用来描述数字、索引引用、数量值或UTF-8字符串。

    2、表:由多个无符号数或者表组成。整个Class文件可看作是一张表。

    3、数量不确定时,必须有一个前置的数量说明。

2、Magic Number和版本号

  魔数:标识该文件是一个Class文件。

  版本号:Minor VersionMajor Version,标识该Class文件的版本(能执行该Class文件的JVM最低版本)。

  Java版本号从45开始,每一个JDK大版本发布,主版本号加1。

3、常量池

   

  可以理解为Class文件中的资源仓库,通常是Class文件中最占空间的一部分。被其他部分所引用,常量池元素之间也存在相互引用。就如同搭积木一样。

  (只有)常量池计数从1开始,0留出是为了表示不引用任何常量池元素的情况。

  两大类型常量:

    1、字面量Literal:基本类型和字符串。

    2、符号引用(引用其他常量池元素)Symbolic Reference

      1、类和接口的全限定名;

      2、字段的名称和描述符;

      3、方法的名称和描述符。

  Java编译过程没有链接步骤,Class文件没有保存各个方法、字段在内存中的布局信息,要依靠虚拟机加载Class文件的时候进行动态链接

  虚拟机运行时,需要从常量池中获得对应的符号引用,再在类创建或运行时解析、翻译到具体的内存地址之中。

4、访问标志

  通过标志位用两个字节表示。

  

5、类索引、父类索引和接口索引集合

  这三个索引用于确定类的继承关系

  类索引、父类索引和接口索引集合按顺序排列在访问标志之后,其中类索引和父类索引各用一个u2类型的索引值表示,指向常量池中的一个CONSTANT_Class_info元素。

  由于可以实现多个接口,因此,要增加一个接口计数器,随后紧接着接口索引集合。

  接口索引同样用一个u2类型的索引值表示,指向常量池中的一个CONSTANT_Class_info元素。

 6、字段表集合

  

 

 

  name_index引用常量池中的元素,为字段的简单名

  descriptor_index引用常量池中的元素,为字段的描述符(包括数据类型)。

  描述符用于描述字段的数据类型,方法的参数列表(包括数量、类型、顺序)和返回值

  由于字段(方法)名已经有单独的属性描述了,因此描述符里不再包括。

  

  描述方法时,按照先参数列表,后返回值的顺序。参数列表严格按照参数顺序放在“()”之内。void 对应的描述符为V。

  描述符示例:

    F: int i;              D: I

    F: java.lang.String[][] S;        D:  [[Ljava/lang/String

    M: void inc();                 D: ()V 

    M: double add(double x, double y);   D: (DD)D

    M: int add(int[] array);                             D: ([I)I

    M: int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex);

    D: ([CII[CIII)I

  字段表不会包含继承而来的字段,但可能包含Java代码中没有的字段(如内部类中包含外部类的引用)。

7、方法表集合

  方法表的结构和字段表一样,只是每个属性的具体内容有所变化(如访问字段变了)。

  如果子类方法表不会包含父类方法,除非子类重写了那个方法。

  可能出现代码中没有的方法,如编译器添加的类构造器<cinit>方法和实例构造器<init>方法。

  Java中特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,返回值不包含在特征签名中,因此Java无法通过返回值区分方法重载。

  但是在Class文件格式中,特征签名还包括方法返回值和受查异常表,因此只有返回值不同的两个方法也能在Class文件中共存。

  编译后的字节码去哪儿了?

    在方法属性表集合中的Code属性里面。

8、属性表集合

  属性表都有固定的两项

    1、u2类型的attribute_name_index,指向常量池中CONSTANT_Utf8_info 型常量的索引,代表属性名。

    2、u4类型的attribute_length,表示后面的attribute_info的数量。 

  1、code属性-方法表

    javac编译器编译处理后的字节码指令,一个字节表示一个指令。

    并非所有方法都存在Code属性,抽象方法就没有。

     

    exception_table:

    

 

  2、Exceptions属性-方法表

    与Code属性平级的属性,不是exception_table !

    

 

  3、LineNumberTable属性-Code属性

    描述Java源码与字节码偏移量的对应关系,用于调试。

    可在编译时加 -g:none 或 -g:lines 参数关闭。

  4、LocalVariableTable属性-Code属性

    描述栈帧中局部变量表与Java源码中定义的变量之间的关系

    可在编译时加 -g:none 或 -g:vars 参数关闭。

  5、SourceFile属性-类文件

    记录Class文件对应的源码文件的名称。

    可在编译时加 -g:none 或 -g:source 参数关闭。

  6、ConstantValue属性-字段表

    记录编译时常量的值。

  7、InnerClasses 属性

    记录内部类与宿主类的关联。

  8、Deprecated和Synthetic属性

    Deprecated类似于@Deprecated。

    Synthetic表示此字段或方法不是Java源码直接生成的。

  9、StackMapTable 属性

    用于类加载时的类型检查

  10、Signiture属性

    用于在擦除法后获取泛型的信息。

  11、BootstrapMethods属性

    用于保存invokedynamic指令引用的引导方法限定符。

9、字节码指令简介

  Java字节码指令和C/C++汇编不太一样,它没有寄存器的概念。而是采用了操作数栈来进行数据的传递和运算

  同时,一个字节序列就是一个指令。因此最多只能有256个指令。因此,不是每种类型数据都有对应的运算指令。。

  可以分为:

  1、加载和存储指令

  2、运算指令

  3、类型转换指令

  4、对象创建与访问指令

  5、操作数栈管理指令

  6、控制转移指令

  7、方法调用和返回指令

  8、异常处理指令

  9、同步指令