目录
一、magic
二、minor_version、major_version
三、constant_pool_count、constant_pool
四、access_flags
五、this_class、super_class
六、interfaces_count、interfaces
七、fileds_count、fields
八、methods_count、methods
九、attributes_count、attributes
本文大部分都是参考《深入理解Java虚拟机 JVM高级特性与最佳实践》一书,写下这篇博客的主要目的主要是加深自己的理解。
首先是类的整体结构
其中,u1、u2、u4、u8表示1个字节、2个字节、4个字节和8个字节的无符号数。
文章的作者举了一个例子来进行讲解,我也直接引用他的例子。
package com.zhuyun.Maven;
public class TestClass {
private int m;
public int inc(){
return m + 1;
}
}
其编译后生成的Class内容如下:
下面按照Class结构的顺序进行分析。
最开始的4个字节是魔数0xCAFEBABE
接下的4个字节是次版本号和主版本号,分别是0x0000、0x0034,其对应的十进制版本号是52,对应关系如下图。
对应的JDK版本号是1.8。
接下的2个字节和不定长字节,表示常量池。
前两个字节表示常量池中常量的个数,从1开始计数,所以一共有21项常量。
这些常量的第一字节是标识位,代表当前常量是属于哪种常量类型,其对应关系如下:
第一个常量:0x07
从表中可以看出,它的常量类型是CONSTANT_Class_info,其结构如下:
tag表示常量类型,就是前面的0x07。
name_index表示索引值,0x0002表示常量池的第二个常量,所以继续往后找。
第二个常量:0x01
从表中可以看出,0x01的常量类型是CONSTANT_Utf8_info,其结构如下:
第一个字节是tag(0x01),后两个字节表示UTF-8的字符串长度,0x001a,十进制是26。
接下来的26个字节,就是 字符串的内容com/zhuyun/Maven/TestClass。
第三个常量:0x07
常量类型是CONSTANT_Class_info,索引值是4,也就是接下来的一个变量。
第四个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x10,十进制是16,字符串内容是java/lang/Object 。
第五个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x01,十进制是1,字符串内容是m。
第六个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x01,十进制是1,字符串内容是I。
第七个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x0006,十进制是6,字符串内容是
第八个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x0003,十进制是3,字符串内容是()V。
第九个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x0004,十进制是4,字符串内容是Code。
第十个常量:0x0a
常量类型是CONSTANT_Methodret_info,其结构如下:
第一个字节是0x0a,表示常量类型是CONSTANT_Methodret_info。
接下来2个字节表示索引值0x0003,也就是指向了第三个变量,即前面的 java/lang/Object,表示方法所属的类。
接下来2个字节表示索引值0x000b,也就是指向了第11个变量,即下一个变量。
第十一个常量:0x0c
常量类型是CONSTANT_NameAndType_info,其结构如下:
第一个字节是0x0c,表示常量类型是CONSTANT_NameAndType_info。
接下来2个字节表示索引值0x0007,也就是指向了第7个变量,即前面的
接下来2个字节表示索引值0x0008,也就是指向了第8个变量,即前面的()V,表示刚方法的返回类型是void。
第十二个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x000f,十进制是15,字符串内容是LineNumberTable。
第十三个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x0012,十进制是18,字符串内容是LocalVariableTable。
第十四个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x0004,十进制是4,字符串内容是this。
第十五个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x001c,十进制是28,字符串内容是Lcom/zhuyun/Maven/TestClass;。
第十六个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x0003,十进制是3,字符串内容是inc。
第十七个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x0003,十进制是3,字符串内容是()I。
第十八个常量:0x09
常量类型是CONSTANT_Filedref_info,表示字段的符号引用,其结构如下。
第一个字节是0x09,表示常量类型是CONSTANT_Filedref_info。
接下来2个字节表示索引值0x0001,也就是指向了第1个变量,即前面的 com/zhuyun/Maven/TestClass,表示字段所属的类。
接下来2个字节表示索引值0x0013,也就是指向了第19个变量,即下一个变量,表示了该字段的描述符。
第十九个常量:0x0c
常量类型是CONSTANT_NameAndType_info,其结构如下:
第一个字节是0x0c,表示常量类型是CONSTANT_NameAndType_info。
接下来2个字节表示索引值0x0005,也就是指向了第5个变量,即前面的m,表示该字段的名称。
接下来2个字节表示索引值0x0006,也就是指向了第6个变量,即前面的I,表示该变量的数据类型是int。
第二十个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x000a,十进制是10,字符串内容是SourceFile。
第二十一个常量:0x01
常量类型是CONSTANT_Utf8_info,字符串长度是0x000e,十进制是14,字符串内容是TestClass.java。
下面2个字节是访问标志,用于识别一些类或者接口层次的访问信息,包括:这个class是类还是接口;是否定义为public类型;是否定义为abstract类型;是否被声明为final等。
首先,将0x0021转成二进制
0000 0000 0010 0001
从高到低,标志位依次的含义:
1 | |
2 | ACC_ENUM |
3 | ACC_ANNOTATION |
4 | ACC_SYNTHETIC |
5 | |
6 | ACC_ABSTRACT |
7 | ACC_INTERFACE |
8 | |
9 | |
10 | |
11 | ACC_SUPER |
12 | ACC_FINAL |
13 | |
14 | |
15 | |
16 | ACC_PUBLIC |
所以只有第11位和13位是1,也就是ACC_SUPER和ACC_PUBLIC为真。
接下来4个字节表示类引用以及父类引用。除了java.lang.Object外,所有Java类都有父类,因此除了java.lang.Object外,所有Java类的父类引用都不为0。
从前面的常量池中可以找到,类引用是com/zhuyun/Maven/TestClass,而父类引用是java/lang/Object。
接下来是接口索引集合,用来描述这个类实现了哪些接口。在该例子中,未实现任何接口所以前2个字节是0,后面的interface也就不占用任何字节。
前2个字节表示字段的个数,该例子中只有一个m字段,所以是1。
后面表示字段表集合,用来描述接口或者类中声明的变量。其结构如下:
前2个字节表示访问标志,0x0002表示字段是private,其他访问标志都是false,其含义如下:
接下来的name_index和descriptor_index表示字段的名称以及字段的描述符的索引。
从前面的常量表中可以知道,该字段的名称是第五个常量m,字段的类型是第六个常量I,其对应的基本类型是int。
接下来的2个字节表示属性表集合,用于存储一些额外的信息。在本例中属性表计数器为0,没有额外描述信息。
前2个字节表示方法的个数,该字段是0x0002,所以有两个方法,一个是本身的方法inc(),另一个自动添加的构造方法
后面表示方法表集合,与上面的字段表类似,其结构如下:
第一个方法:
前2个字节表示访问标志,0x0001表示方法是public的,其他访问标志都是false,其含义如下:
接下来的name_index和descriptor_index表示方法的名称以及方法的描述符的索引。
从前面的常量表中可以知道,该方法的名称是第7个常量
属性表集合,其结构如下:
接下来的2个字节表示属性表集合,用于存储一些额外的信息。0x0001表示只有一个属性表。
接下来2个字节表示属性名称索引,0x0009,对应的常量是Code,说明此属性是方法的字节码描述。
其结构如下:
attribute_name_index:前2个字节,也就是0x0009,表示Code
attribute_length:接下来4个字节,0x0000002f,表示属性值长度。
max_stack:操作数栈深度的最大值,该例子中为1。
max_locals:局部变量所需的存储空间,单位是slot,该例子中为1。
code_length: 字节码长度,该例子中为5,表示有5条字节码指令。
code:字节码指令,根据前面的code_length,获取接下来的code_length个字节作为字节码指令。
查表可知:
exception_table_length:异常处理表长度,该例子中不存在异常处理,长度为0。
接下来解析类似。
接下来的2个字节表示属性表集合,用于存储一些额外的信息。0x0002表示有2个属性表。
接下来2个字节表示属性名称索引,0x000c,对应的常量是LineNumberTable,此属性是用于描述Java源码行号与字节码行号之间的对应关系。
其结构如下:
attribute_name_index:前2个字节,也就是0x000c,表示LineNumberTable
attribute_length:接下来4个字节,0x00000006,表示属性值长度,目前是6个字节。
line_number_table_length:接下来的2个字节,0x0001,表示line_number_info的个数。
line_number_info:包括了start_pc和line_number两个u2类型的数据项,前者是字节码行数,后者是Java源码行号。该例子中 其值是0x0000 0003。
继续往后解析,接下来2个字节表示属性名称索引,0x000d,对应的常量是LocalVariableTable,此属性是用于描述栈帧中局部变量表中的变量Java源码中定义的变量之间的关系。
其结构如下:
attribute_name_index:前2个字节,也就是0x000d,表示LocalVariableTable
attribute_length:接下来4个字节,0x0000000c,表示属性值长度,目前是12个字节。
local_variable_table_length:接下来的2个字节,0x0001,表示local_variable_table的个数。
local_variable_table:代表了一个栈帧与源码中的局部变量的关联,结构如下:
start_pc:这个局部变量的生命周期开始的字节码偏移量。该例子中为0x0001。
length:这个局部变量的生命周期开始的字节码的作用范围覆盖的长度。该例子中为0x0000。
name_index:局部变量名称的索引,该例子中为0x0005,具体的值为m。
descriptor_index:局部变量的描述符的索引,该例子中为0x000e,具体的值为this。
index:局部变量在栈帧局部变量表中Slot的位置,该例子中为0x000f。