JVM学习笔记——Class类文件解读

简述

Java源代码通过编译生成.class文件字节码后再被JVM解释转化为目标机器代码,从而实现一次编写到处,到处运行("Write Once,Run Anywhere")。字节码与平台无关,而且并不是只有Java语言编译为字节码文件在虚拟机上运行。

类文件的结构

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中。Class文件只有两种数据类型:无符号数和表。

无符号数属于基本的数据类型,有u1, u2, u4, u8,分别代表1个字节、2个字节、4个字节和8个字节的无符号数

整个Class文件就是一张表,由以下数据项构成:

类型 名称 数量 u4 magic(魔数) 1 u2 minor_version(次版本号) 1 u2 major_version(主版本号) 1 u2 constant_pool_count(常量池容量) 1 cp_info constant_pool(常量池) constant_pool_count-1 u2 access_flags(访问标志) 1 u2 this_class(类索引) 1 u2 super_class(父类索引) 1 u2 interfaces_count(接口容量) 1 u2 interfaces(接口) interfaces_count u2 fields_count(字段容量) 1 field_info fields(字段) fields_count u2 methods_count(方法容量) 1 mehtod_info methods(方法) method_count u2 attributes_count(属性容量) 1 attribute attributes(属性) attributes_count 小试牛刀

写个简单实体类,javac编译后,查看其字节码

源码

public class Person { private int age; public int getAge(){ return age; } public static synchronized void work() { System.out.println("工作"); } public static void main(String[] args) { }}

十六进制Class文件

根据上述数据项表格我们按顺序拆分

魔数

魔数站每个Class文件的头4个字节,其作用未确定确定这个文件是否为一个能被虚拟机接受的Class文件 示例中CA FE BA BE为魔数

版本号

魔数后面紧跟着版本号

00 00——次版本号

00 34——主版本号

根据如下:

十进制版本号 主版本 jdk1.8 52 jdk1.7 51 jdk1.6 50 jdk1.5 49 jdk1.4 48 jdk1.3 47 jdk1.2 46 jdk1.1 45 十六进制0034,对应十进制52,对应jdk1.8版本

常量池

常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型。常量池中主要存放两大类常量:字面量和符号引用.

字面量——接近于Java中的常量概念,eg:final修饰、文本字符串

符号引用——编译原理概念:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符

常量池中的每一项常量都是一个表,每种常量都有自己的结构,14种常量含义:

类型 标志 描述 CONSTANT_utf8_info 1 utf-8编码的字符串 CONSTANT_Integer_info 3 整型字面量 CONSTANT_Float_info 4 浮点型字面量 CONSTANT_Long_info 5 长整型字面量 CONSTANT_Double_info 6 双精度浮点型字面量 CONSTANT_Class_info 7 类或者接口的符号引用 CONSTANT_String_info 8 字符串型字面量 CONSTANT_Fieldref_info 9 字段的符号引用 CONSTANT_Methodref_info 10 类中方法的符号引用 CONSTANT_InterfaceMethoderf_info 11 接口中方法的符号引用 CONSTANT_NameAndType_info 12 字段或方法的部分符号引用 CONSTANT_MethodHandle_info 15 表示方法句柄 CONSTANT_MethodType_info 16 标识方法类型 CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点 0×0029转十进制为41,代表常量池中有40项常量(容量计数是从1而不是0开始。第0项常量空出来是表达“不引用任何一个常量池项目”)

0A即十进制10,对应表中CONSTANT_Methodref_info,其结构如下:

类型 名称 描述 u1 tag 值为10 u2 index 指向声明方法的类描述符CONSTANT_Class_info的索引项 u2 index 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 0×0007为常量池中第7项CONSTANT_Class_info,0×001A为第26项CONSTANT_NameAndType_info。按照《深入理解Java虚拟机》第二版,172页中表6-6顺序解析得:加群617434785里面有文中整理的知识点

00 29 //constant_pool_count(常量池容量) #1、0A 0007 001A //CONSTANT_Methodref_info,#7,#26 #2、09 0006 001B //CONSTANT_Fieldref_info,#6,#27 #3、09 001C 001D //CONSTANT_Fieldref_info,#28,#29 #4、08 001E //CONSTANT_String_info,#30 #5、0A 001F 0020 //CONSTANT_Methodref_info,#31,#32 #6、07 0021 //CONSTANT_Class_info,#33 #7、07 0022 //CONSTANT_Class_info,#34 #8、01 0003 61 67 65 //CONSTANT_Utf8_info,3个字节,age #9、01 0001 49 //CONSTANT_Utf8_info,1个字节,I#10、01 0006 3C 69 6E 69 74 3E //CONSTANT_Utf8_info,6个字节,#11、01 0003 28 29 56 //CONSTANT_Utf8_info,3个字节,()V#12、01 0004 43 6F 64 65 //CONSTANT_Utf8_info,4个字节,Code#13、01 000F 4C 69 6E 65 4E 75 6D 62 //CONSTANT_Utf8_info,15个字节,LineNumberTable 65 72 54 61 62 6C 65 #14、01 0012 4C 6F 63 61 6C 56 61 72 //CONSTANT_Utf8_info,18个字节,LocalVariableTable 69 61 62 6C 65 54 61 62 6C 65#15、01 0004 74 68 69 73 //CONSTANT_Utf8_info,4个字节,this#16、01 0008 4C 50 65 72 73 6F 6E 3B //CONSTANT_Utf8_info,8个字节,LPerson;#17、01 0006 67 65 74 41 67 65 //CONSTANT_Utf8_info,6个字节,getAge#18、01 0003 28 29 49 //CONSTANT_Utf8_info,3个字节,()I#19、01 0004 77 6F 72 6B //CONSTANT_Utf8_info,4个字节,work#20、01 0004 6D 61 69 6E //CONSTANT_Utf8_info,4个字节,main#21、01 0016 28 5B 4C 6A 61 76 61 2F //CONSTANT_Utf8_info,22个字节,([Ljava/lang/String;)V 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56#22、01 0004 61 72 67 73 //CONSTANT_Utf8_info,4个字节,args#23、01 0013 5B 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info,19个字节,[Ljava/lang/String; 61 6E 67 2F 53 74 72 69 6E 67 3B#24、01 000A 53 6F 75 72 63 65 46 69 //CONSTANT_Utf8_info,10个字节, SourceFile 6C 65#25、01 000B 50 65 72 73 6F 6E 2E 6A //CONSTANT_Utf8_info,11个字节, Person.java 61 76 61#26、0C 000A 000B //CONSTANT_NameAndType_info,#10,#11#27、0C 0008 0009 //CONSTANT_NameAndType_info,#8,#9#28、07 0023 //CONSTANT_Class_info,#35#29、0C 0024 0025 //CONSTANT_NameAndType_info,#36,#37#30、01 0006 E5 B7 A5 E4 BD 9C //CONSTANT_Utf8_info,6个字节,工作#31、07 0026 //CONSTANT_Class_info,#38#32、0C 0027 0028 //CONSTANT_NameAndType_info,#39,#40#33、01 0006 50 65 72 73 6F 6E //CONSTANT_Utf8_info,6个字节,Person#34、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info,16个字节,java/lang/Object 67 2F 4F 62 6A 65 63 74#35、01 0010 6A 61 76 61 2F 6C 61 6E //CONSTANT_Utf8_info,16个字节, java/lang/System 67 2F 53 79 73 74 65 6D#36、01 0003 6F 75 74 //CONSTANT_Utf8_info,3个字节,out#37、01 0015 4C 6A 61 76 61 2F 69 6F //CONSTANT_Utf8_info,21个字节, Ljava/io/PrintStream; 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B#38、01 0013 6A 61 76 2F 69 6F 2F 50 //CONSTANT_Utf8_info,19个字节,java/io/PrintStream 72 69 6E 74 53 74 72 65 61 6D#39、01 0007 70 72 69 6E 74 6C 6E //CONSTANT_Utf8_info,7个字节,println#40、01 0015 28 4C 6A 61 76 61 2F 6C //CONSTANT_Utf8_info,21个字节, (Ljava/lang/String;)V 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56

也可以java -verbose分析Class文件字节码,得到结果:

Constant pool: #1 = Methodref #7.#26 // java/lang/Object."":()V #2 = Fieldref #6.#27 // Person.age:I #3 = Fieldref #28.#29 // java/lang/System.out:Ljava/io/PrintStream; #4 = String #30 // 工作 #5 = Methodref #31.#32 // java/io/PrintStream.println:(Ljava/lang/String;)V #6 = Class #33 // Person #7 = Class #34 // java/lang/Object #8 = Utf8 age #9 = Utf8 I #10 = Utf8 #11 = Utf8 ()V #12 = Utf8 Code #13 = Utf8 LineNumberTable #14 = Utf8 LocalVariableTable #15 = Utf8 this #16 = Utf8 LPerson; #17 = Utf8 getAge #18 = Utf8 ()I #19 = Utf8 work #20 = Utf8 main #21 = Utf8 ([Ljava/lang/String;)V #22 = Utf8 args #23 = Utf8 [Ljava/lang/String; #24 = Utf8 SourceFile #25 = Utf8 Person.java #26 = NameAndType #10:#11 // "":()V #27 = NameAndType #8:#9 // age:I #28 = Class #35 // java/lang/System #29 = NameAndType #36:#37 // out:Ljava/io/PrintStream; #30 = Utf8 工作 #31 = Class #38 // java/io/PrintStream #32 = NameAndType #39:#40 // println:(Ljava/lang/String;)V #33 = Utf8 Person #34 = Utf8 java/lang/Object #35 = Utf8 java/lang/System #36 = Utf8 out #37 = Utf8 Ljava/io/PrintStream; #38 = Utf8 java/io/PrintStream #39 = Utf8 println #40 = Utf8 (Ljava/lang/String;)V

访问标志

在常量池之后紧接着两个字节代表访问标志,用于识别一些类或者接口层次的访问信息

具体的标志位和含义如下:

名称 标志值 含义 ACC_PUBLIC 0×0001 是否为public ACC_FINAL 0x0010 是否为final ACC_SUPER 0x0020 JDK 1.0.2之后编译出来的类这个标志都为真 ACC_INTERFACE 0x0200 是否为一个接口 ACC_ABSTRACT 0x0400 是否为abstract类型 ACC_SUPER 0x0020 JDK 1.0.2之后编译出来的类这个标志都为真 ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生 ACC_ANNOTATION 0x2000 是否是注解 ACC_ENUM 0x4000 是否是枚举 没有使用到的标志位要求一律为0,本例access_flags的值为:ACC_PUBLIC | ACC_SUPER = 0x0021

类索引、父类索引与接口索引

类索引、父类索引与接口索引(指向常量池)都是u2类型的数据,除了java.lang.Object 之外所有的Java类都有父类,没有实现接口计数器为0,本例:

0006 //this_class Person 0007 //super_class java/lang/Object 0000 //没有实现结构故0

字段表集合

字段表用于描述类和接口中声明的变量。字段包括类级变量和实例级变量,但是不包括方法中的变量。字段信息:字段的作用域,public/private/protected 实例变量还是类变量,static 可变性,final 并发可见性, volatile 可否被序列化, transient,字段数据类型(基本类型,对象,数组),字段名称

字段表结构:

类型 名称 数量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_count

对于本例:

0001 //fields_count 字段容量即1个字段 0002 //访问标志 private 0008 //常量池第8项,即age 0009 //字段描述符,常量池第9项,即I 0000 //attribute_count

方法表集合

Class文件存储格式中对方法的描述与对字段的描述几乎采用完全一致的方式。

方法表结构:

类型 名称 数量 u2 access_flags 1 u2 name_index 1 u2 descriptor_index 1 u2 attributes_count 1 attribute_info attributes attributes_count

对于本例:

0004 //方法容量,即4个方法:实例构造器、getAge()、work以及main方法 0001 //方法访问标志,public 000A //常量池第10项, 000B //方法描述常量池第11个,()V,没返回值 0001 //attribute_count 000C //常量池第12项,Code属性表,存放方法里的Java代码 0000002F //属性表长度 47 ... 47个字节后0001 //方法访问标志,public0011 //常量池第17项,getAge0012 //方法描述常量池第18个,()I 返回int型0001 //attribute_count000C //常量池第12项,Code属性表,存放方法里的Java代码0000002F //属性表长度 47... 47个字节后0029 // ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED 0×0001|0×0008|0×00200013 //常量池第19项,work000B //方法描述常量池第11个,()V,没返回值0001 //attribute_count000C //常量池第12项,Code属性表,存放方法里的Java代码00000025 //属性表长度 37...37个字节后0009 //ACC_PUBLIC, ACC_STATIC 0×0001|0×00080014 //常量池第20项,main0015 //方法描述常量池第21项,([Ljava/lang/String;)V String数组形参,无返回类型方法0001 //attribute_count000C //常量池第12项,Code属性表,存放方法里的Java代码0000002B //属性表长度 43

属性表集合

对于本例:

构造方法:

000C //常量池第12项,Code属性 0000002F //Code属性表长度 47 0001 //max_stack 操作数栈深度最大值 1 0001 //max_locals 局部变量存储 00000005 //code_length 字节码长度 2A B7 00 01 B1 //字节码指令 0000 //exception_table_length 0002 //attributes_count 2个属性 000D //常量池第13项, LineNumberTable属性 00000006 //LineNumberTable属性表长度 0001 //line_number_table_length 0000 //start_pc 字节码行号 0001 //line_number Java源码行号 000E //常量池第14项,LocalVariableTable属性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 这个局部变量的生命周期开始的字节码偏移量 0005 //局部变量作用范围覆盖的长度 000F //name_index 局部变量名称 常量池第15项,this 0010 //descriptor_index 局部变量描述 常量池第16项,LPerson; 0000 //这个局部变量在栈帧局部变量表中Slot的位置

getAge方法

000C //常量池第12项,Code属性 0000002F //attribute_length 0001 //max_stack 操作数栈深度最大值 1 0001 //max_locals 局部变量存储 00000005 //code_length 字节码长度 2A B4 00 02 AC //字节码指令 0000 //exception_table_length 0002 //attributes_count 2个属性 000D //常量池第13项, LineNumberTable属性 00000006 //LineNumberTable属性表长度 0001 //line_number_table_length 0000 //start_pc 字节码行号 0006 //line_number Java源码行号 000E //常量池第14项,LocalVariableTable属性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 这个局部变量的生命周期开始的字节码偏移量 0005 //局部变量作用范围覆盖的长度 000F //name_index 局部变量名称 常量池第15项,this 0010 //descriptor_index 局部变量描述 常量池第16项,LPerson; 0000 //这个局部变量在栈帧局部变量表中Slot的位置

work方法

000C //常量池第12项,Code属性 00000025 //attribute_length 0002 //max_stack 操作数栈深度最大值 2 0000 //max_locals 局部变量存储 00000009 //code_length 字节码长度 B2 00 03 12 04 B6 00 05 B1 //字节码指令 0000 //exception_table_length 0001 //attributes_count 1个属性 000D //常量池第13项, LineNumberTable属性 0000000A //LineNumberTable属性表长度 0002 //line_number_table_length 2个line_number_info 0000 //start_pc 字节码行号 000A //line_number Java源码行号 0008 //start_pc 字节码行号 000B //line_number Java源码行号

main方法

000C //常量池第12项,Code属性 0000002B //attribute_length 0000 //max_stack 操作数栈深度最大值 0 0001 //max_locals 局部变量存储 00000001 //code_length 字节码长度 B1 //字节码指令 0000 //exception_table_length 0002 //attributes_count 2个属性 000D //常量池第13项, LineNumberTable属性 00000006 //LineNumberTable属性表长度 0001 //line_number_table_length 1个line_number_info 0000 //start_pc 字节码行号 000F //line_number Java源码行号 000E //常量池第14项,LocalVariableTable属性 0000000C //attribute_length 0001 //local_variable_table_length 0000 //start_pc 这个局部变量的生命周期开始的字节码偏移量 0001 //局部变量作用范围覆盖的长度 0016 //name_index 局部变量名称 常量池第22项,args 0017 //descriptor_index 局部变量描述 常量池第23项,[Ljava/lang/String; 0000 //这个局部变量在栈帧局部变量表中Slot的位置

总结

本篇做了一个小小的尝试,按照数据项表格一一解析,感兴趣的同学可以读下《深入理解Java虚拟机》这本圣书。

如果想学习Java工程化、高性能及分布式、深入浅出。微服务、Spring,MyBatis,Netty源码分析的朋友可以加我的Java进阶群:617434785,群里有阿里大牛直播讲解技术,以及Java大型互联网技术的视频免费分享给大家。

猜你喜欢

转载自blog.csdn.net/lecter_13/article/details/80816344