18、JVM实战:class文件解析

18–class文件解析


1、前提

1.1、测试的代碼

public class Hello{
    private int test;
    public int test(){
        return test;
    }
}

1.2、对应的二进制代码

CA FE BA BE 00 00 00 34 00 12 0A 00 04 00 0E 09 00 03 00 0F 07 00 10 07 00 11 01 00 04 74 65 73 74 01 00 01 49 01 00 06 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 01 00 03 28 29 49 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0A 48 65 6C 6C 6F 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 05 48 65 6C 6C 6F 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 02 00 01 00 07 00 08 00 01 00 09 00 00 00 1D 00 01 00 01 00 00 00 05 2A B7 00 01 B1 00 00 00 01 00 0A 00 00 00 06 00 01 00 00 00 01 00 01 00 05 00 0B 00 01 00 09 00 00 00 1D 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 01 00 0A 00 00 00 06 00 01 00 00 00 04 00 01 00 0C 00 00 00 02 00 0D

2、Class文件的结构

1、 解读二进制文件的时候按照下面Class文件的结构的顺序解读;
2、 u1,u2,u4,u8分别表示一种数据项在class文件中占据1,2,4,8个字节;

*

2.1、magic

1、 class文件开头的四个字节;
2、 固定的值:0XCAFEBABE;
3、 用于判断一个文件是不是class格式,如果开头四个字节不是0XCAFEBABE,那么就说明它不是class文件,不能被JVM识别;

*

2.2、minor_version 和 major_version

1、 按照class文件结构的顺序,接下来是2个字节的minor_version和major_version,对应次版本号和主版本号;

*

表示:次版本号为0,主版本号为52,可以对应jdk版本为1.8

2.2.1、JDK版本和版本号关系

*

2.3、constant_pool

2.3.1、常量池

1、 几乎包含类中的所有信息的描述;
2、 是一个类的结构索引,其它地方对"对象"的引用可以通过索引位置来代替,我们知道在程序中一个变量可以不断地被调用,要快速获取这个变量常用的方法就是通过索引变量这种索引我们可以直观理解为"内存地址的虚拟"我们把它叫静态池的意思就是说这里维护着经过编译"梳理"之后的相对固定的数据索引,它是站在整个JVM(进程)层面的共享池;
3、 class文件中的项constant_pool_count的值为1,说明每个类都只有一个常量池;
4、 常量池中的数据也是一项一项的,没有间隙的依次排放常量池中各个数据项通过索引来访问,有点类似与数组,只不过常量池中的第一项的索引为1,而不为0,如果class文件中的其他地方引用了索引为0的常量池项,就说明它不引用任何常量池项常量池中的每一种数据项有自己的类型;

2.3.2、常量池中的数据项的类型如下表

*

*

*

扩展

1、 每个数据项叫做一个XXX_info项,比如;

1、 上面常量池中一个CONSTANT_Utf8类型的项,就是一个CONSTANT_Utf8_info;
2、 每个info项中都有一个tag,这个tag表明了这个常量池中的info项的类型是什么,比如;

1、 上面CONSTANT_NameAndType_info中的tag值为12,表示他的类型是12;
3、 常量池中还存放着以下几种符号引用:;

1、 类和接口的全限定名;
2、 字段的名称和描述符;
3、 方法的名称和描述符;
4、 我们有必要先了解一下class文件中的特殊字符串,因为在常量池中,特殊字符串大量的出现,这些特殊字符串就是上面说的全限定名和描述符;

2.4、access_flag

保存了当前类的访问权限,对应的访问标示符的规范如下。

*

2.5、this_cass

保存了当前类的全局限定名在常量池里的索引

2.6、super class

保存了当前类的父类的全局限定名在常量池里的索引

2.7、interfaces

保存了当前类实现的接口列表,包含两部分内容

1、 interfaces_count;

1、 指的是当前类实现的接口数目;
2、 interfaces[interfaces_count];

1、 包含interfaces_count个接口的全局限定名的索引的数组;

2.8、fields

2.8.1、保存了当前类的成员列表,包含两部分的内容

1、 fields_count;

1、 类变量和实例变量的字段的数量总和;
2、 fields[fields_count];

1、 包含字段详细信息的列表;

2.8.2、结构如下

field_info {
                 u2 access_flags; 
                 u2 name_index; 
                 u2 descriptor_index; 
                 u2 attributes_count;
                 attribute_info attributes[attributes_count];
       }

2.9、methods

2.9.1、保存了当前类的方法列表,包含两部分的内容

1、 methods_count;

1、 该类或者接口显示定义的方法的数量;
2、 methods[methods_count];

1、 是包含方法信息的一个详细列表;

2.9.2、结构如下

method_info {
     u2 access_flags;
     u2 name_index;
     u2 descriptor_index;
     u2 attributes_count;
     attribute_info attributes[attributes_count];
}

2.10、attributes

1、 class文件的最后一部分是属性,它描述了该类或者接口所定义的一些属性信息;
2、 属性可以出现在class文件的很多地方,而不只是出现在attributes列表里;

1、 如果是attributes表里的属性,那么它就是对整个class文件所对应的类或者接口的描述;
2、 如果出现在fileds的某一项里,那么它就是对该字段额外信息的描述;
3、 如果出现在methods的某一项里,那么它就是对该方法额外信息的描述;
3、 包含两部分内容;

1、 attributes_count;

1.  attributes列表中包含的attribute\_info的数量。

2、 attributes[attributes_count];

1.  是包含属性信息的一个详细列表。

3、解析二进制文件

按照class结构的顺序解析。

3.1、magic

*

*

![*][nbsp 10]

3.2、version字段

![*][nbsp 11]

![*][nbsp 12]

3.3、常量数目

![*][nbsp 13]

![*][nbsp 14]

因为字节码的常量池是从1开始计数的,这个常量池包含17个(0x0012-1)常量。

接下来就是分析这17个常量

3.3.1、第一个变量

![*][nbsp 15]

第一个变量 0A,(tag=10),对应的结构是
![*][nbsp 16]

结构中有2个u2,表示接下有4个字节是这个常量的内容。

![*][nbsp 17]

0A: //tag  10表示这是一个CONSTANT_Methodref_info结构
00 04: //class_index 指向常量池中第4个常量所表示的类
00 0E:  //name_and_type_index 指向常量池中第14个常量所表示的方法

第一个常量解析完,接下里解析第二个常量

3.3.2、第二个常量

![*][nbsp 18]

第2个变量 09,(tag=9),对应的结构是

![*][nbsp 19]

结构中有2个u2,表示接下有4个字节是这个常量的内容。

![*][nbsp 20]

09 //tag
00 03 //指向常量池中第3个常量所表示的类
00 0f //指向常量池中第15个常量所表示的变量
接下来我就不演示了,给出一个常量的最终演示结果如下,框起来的常量,刚好对应17个常量

![*][nbsp 21]

利用工具可以得到
javap -v Hello.class

![*][nbsp 22]

3.4、access_flag(u2)

![*][nbsp 23]

![*][nbsp 24]

这个表里面无法直接查询到0021这个值,原因是0021=0020+0001,也就是表示当前class的access_flag是ACC_PUBLIC|ACC_SUPER。

ACC_PUBLIC:代码里的public 关键字相对应。

ACC_SUPER:当用invokespecial指令来调用父类的方法时需要特殊处理。

3.5、his_class(u2, 00 03)

this_class指向constant pool的索引值

该值必须是CONSTANT_Class_info类型

这里是3,即指向常量池中的第三项,即是“Hello”。

![*][nbsp 25]

3.6、super_class(u2, 00 04)

super_class存的是父类的名称在常量池里的索引

这里指向第4个常量,即是“java/lang/Object”。

![*][nbsp 26]

3.7、interfaces(0)

![*][nbsp 27]

3.7.1、interfaces包含两个字段。

1. interfaces_count
2. interfaces[]
因为这里没有实现接口,所以就不存在interfces选项,这里的interfaces_count也就为0(0000),后面的内容也对应为空。

![*][nbsp 28]

3.8、fields

![*][nbsp 29]

3.8.1、结构如下

field_info {
                 u2 access_flags;
                 u2 name_index; 
                 u2 descriptor_index;
                 u2 attributes_count; 
                 attribute_info attributes[attributes_count];
       }

3.8.2、测试

![*][nbsp 30]

00 01:成员变量的个数,此处为1个
fields对应的结构数据
field_info {
                 u2 access_flags; 0002
                 u2 name_index; 0005
                 u2 descriptor_index; 0006
                 u2 attributes_count; 0000
                 attribute_info attributes[attributes_count];
       }

access_flags为0002,即是ACC_PRIVATE
name_index指向常量池的第五个常量,为"test"
descriptor_index指向常量池的第6个常量为"I"
三个字段结合起来,说明这个变量是"private int test"。
接下来的是attribute字段,用来描述该变量的属性,因为这个变量没有附加属性,所以attributes_count=0,也就是attribute_info为空。

![*][nbsp 31]

3.9、methods

![*][nbsp 32]

3.9.1、结构

method_info {
     u2 access_flags;
     u2 name_index;
     u2 descriptor_index;
     u2 attributes_count;
     attribute_info attributes[attributes_count];
}

3.9.2、测试

![*][nbsp 33]

0002 表示method_count,也就是方法的数量

为什么会有两个方法呢?我们明明只写了一个方法,这是因为JVM 会自动生成一个方法,这个是类的默认构造方法。

methods对应的结构数据

![*][nbsp 34]

access_flags为0001,即是ACC_PUBLIC
name_index指向常量池的第7个常量,为  <init>
descriptor_index指向常量池的第8个常量为   V()
三个字段结合起来,说明这个变量是 "public void <init>()"

接下来是attribute字段,也即是这个方法的附加属性,这里的attributes_count =0001,代表有一个属性,每个属性的都是一个attribute_info结构,如下所示:(这是一般情况,还得看0009对应的常量是什么,才可以分析对应的结构)

attribute_info {
     u2 attribute_name_index;
     u4 attribute_length;
     u1 info[attribute_length];
}

JVM预定义了部分attribute,但是编译器自己也可以实现自己的attribute写入class文件里,供运行时使用。
不同的attribute通过attribute_name_index来区分。
JVM规范里对以下attribute进行了预定义:

![*][nbsp 35]

这里的attribute_name_index值为0009,表示指向第9个常量,即是Code。

![*][nbsp 36]

Code Attribute :保存该方法的结构如所对应的字节码
Code Attribute结构:
Code_attribute {
      u2 attribute_name_index;
      u4 attribute_length;
      u2 max_stack;
      u2 max_locals;
      u4 code_length;
      u1 code[code_length];
      u2 exception_table_length;
      {
           u2 start_pc;
           u2 end_pc;
           u2 handler_pc;
           u2 catch_type;
      } exception_table[exception_table_length];
      u2 attributes_count;
      attribute_info attributes[attributes_count];
 }

按结构划分的字节如下

![*][nbsp 37]

attribute_name_index
这里为00 09,表示指向第9个常量,即是Code,对应的结构是Code_attribute
attribute_length:
1. attribute所包含的字节数
2. 这里为0000001D,即是39个字节,不包含attribute_name_index和attribute_length字段。

max_stack
1. 表示这个方法运行的任何时刻所能达到的操作数栈的最大深度,
2. 这里是0001

max_locals
1. 表示方法执行期间创建的局部变量的数目,包含用来表示传入的参数的局部变量
2. 这里是0001

code_length
1. 表示该方法的所包含的字节码的字节数以及具体的指令码。
2. 这里的字节码长度为00000005,即是后面的5个字节 2A B7 00 01为对应的字节码指令的指令码。
3. 对应的翻译是
	1.  2A   aload_0    
		B7   invokespecial
		00   nop
		01   aconst_null
		B1   return
	2. 这是该方法被调用时,虚拟机所执行的字节码
exception_table
1. 这里存放的是处理异常的信息。
2. exception_table表组成
	1. start_pc: code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理
	2. end_pc:code数组中的从start_pc到end_pc处(包含start_pc,不包含end_pc)的指令抛出的异常会由这个表项来处理
	3. handler_pc:  表示处理异常的代码的开始处
	4. catch_type: 表示会被处理的异常类型,它指向常量池里的一个异常类。当catch_type为0时,表示处理所有的异常,这个可以用来实现finally的功能。
3. 这段代码里没有异常处理,所以exception_table_length为0000,所以我们不做分析。
attributes_count

1、 该方法的附加属性;
2、 attributes_count为0001,表示有一个附加属性;
3、 attribute_name_index为000A,指向第十个常量,为LineNumberTable;
4、 这个属性用来表示code数组中的字节码和java代码行数之间的关系;
5、 这个属性可以用来在调试的时候定位代码执行的行数;

![*][nbsp 38]

LineNumberTable的结构如下:
LineNumberTable_attribute {
       u2 attribute_name_index;
       u4 attribute_length;
       u2 line_number_table_length;
       {     
            u2 start_pc;
            u2 line_number;
        } line_number_table[line_number_table_length];
}

attribute_name_index:

attribute的名称是LineNumberTable

attribute_length:

长度为00000006

line_number_table_length:

0001,表示line_number_table有一个表项,其中start_pc为 00 00,line_number为 00 01,表示第1行代码从code的第0个指令码开始。

第二个方法我就不分析了,最后结构如下
CA FE BA BE
00 00 00 34
00 12常量池的数目 18-1=17
0A 00 04 00 0E  方法:java.lang.Ojbect void <init>()
09 00 03 00 0F  方法 :Hello int test()
07 00 10  字符串:Hello
07 00 11  字符串:java.lang.Ojbect
01 00 04 74 65 73 74  字符串:test
01 00 01 49   字符串:I
01 00 06 3C 69 6E 69 74 3E  字符串:<init>
01 00 03 28 29 56 字符串:()V
01 00 04 43 6F 64 65 字符串:Code
01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65 字符串:LineNumberTable
01 00 03 28 29 49 字符串:()I
01 00 0A 53 6F 75 72 63 65 46 69 6C 65  字符串:SourceFile
01 00 0A 48 65 6C 6C 6F 2E 6A 61 76 61 字符串:Hello.java
0C 00 07 00 08 NameAndType:<init> ()V
0C 00 05 00 06 NameAndType:test I
01 00 05 48 65 6C 6C 6F 字符串:Hello
01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74  字符串: java/lang/Object
00 21
00 03
00 04
00 00
00 01 00 02 00 05 00 06 00 00

00 02 method_info
    00 01 access_flags
    00 07 name_index
    00 08 descriptor_index
    00 01 attributes_count
            Code_attribute  
            00 09                 attribute_name_index
            00 00 00 1D         attribute_length
            00 01                max_stack
            00 01                max_locals
            00 00 00 05         code_length
                    2A B7 00 01 B1 code[code_length]
            00 00                 exception_table_length
            00 01                attributes_count
                    LineNumberTable_attribute
                    00 0A          attribute_name_index
                    00 00 00 06    attribute_length
                    00 01        line_number_table_length
                        00 00        start_pc
                        00 01         line_number
    00 01 access_flags
    00 05 name_indexz
    00 0B descriptor_index
    00 01 attributes_count
            Code_attribute
            00 09                 attribute_name_index
            00 00 00 1D         attribute_length
            00 01                 max_stack
            00 01                 max_locals
            00 00 00 05         code_length
                    2A B4 00 02 AC code[code_length]
            00 00                 exception_table_length
            00 01                 attributes_count
                    LineNumberTable_attribute
                    00 0A         attribute_name_index
                    00 00 00 06 attribute_length
                    00 01         line_number_table_length
                        00 00         start_pc
                        00 04        line_number

00 01
    00 0C
    00 00 00 02
    00 0D

3.10、attributes

![*][nbsp 39]

1. 这里的attributes表示整个class文件的附加属性,
2. 结构还是和前面的attribute保持一致。
3. 00 01表示有一个attribute。
结构
SourceFile_attribute {
   u2 attribute_name_index;
   u4 attribute_length;
   u2 sourcefile_index;
}

attribute_name_index:

000C,指向第12个常量,为SourceFile,说明这个属性是Source

![*][nbsp 40]

attribute_length:

0000 00 02,表示2个字节长度

sourcefile_index:

000D,表示指向常量池里的第13个常量,为Hello.java。
![*][nbsp 41]

这个属性表明:当前的class文件是从Hello.java文件编译而来。

[nbsp 10]:
[nbsp 11]: https://cloud.cxykk.com/images/2024/1/27/1320/1706332837995.png
[nbsp 12]: https://cloud.cxykk.com/images/2024/1/27/1320/1706332843961.png
[nbsp 13]: https://cloud.cxykk.com/images/2024/1/27/1320/1706332849915.png
[nbsp 14]: https://cloud.cxykk.com/images/2024/1/27/1320/1706332855875.png
[nbsp 15]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332861897.png
[nbsp 16]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332867824.png
[nbsp 17]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332873796.png
[nbsp 18]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332879670.png
[nbsp 19]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332885332.png
[nbsp 20]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332891025.png
[nbsp 21]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332896643.png
[nbsp 22]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332902430.png
[nbsp 23]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332907953.png
[nbsp 24]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332913515.png
[nbsp 25]: https://cloud.cxykk.com/images/2024/1/27/1321/1706332919091.png
[nbsp 26]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332924969.png
[nbsp 27]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332931039.png
[nbsp 28]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332936808.png
[nbsp 29]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332942856.png
[nbsp 30]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332948854.png
[nbsp 31]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332954674.png
[nbsp 32]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332960586.png
[nbsp 33]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332966167.png
[nbsp 34]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332972264.png
[nbsp 35]: https://cloud.cxykk.com/images/2024/1/27/1322/1706332978064.png
[nbsp 36]: https://cloud.cxykk.com/images/2024/1/27/1323/1706332983872.png
[nbsp 37]: https://cloud.cxykk.com/images/2024/1/27/1323/1706332989849.png
[nbsp 38]: https://cloud.cxykk.com/images/2024/1/27/1323/1706332995931.png
[nbsp 39]: https://cloud.cxykk.com/images/2024/1/27/1323/1706333001607.png
[nbsp 40]: https://cloud.cxykk.com/images/2024/1/27/1323/1706333007401.png
[nbsp 41]: https://cloud.cxykk.com/images/2024/1/27/1323/1706333013208.png