深入理解java虚拟机——类文件结构

深入理解java虚拟机——类文件结构

Class 文件是一组以 8 位字节为基础单位的二进制流,其文件格式采用一种类似C语言「 结构体 」的结构存储数据,这种伪结构体只有两种数据类型:无符号数
无符号数以 u1,u2,u4,u8 分别代表 1 个字节、2 个字节、4 个字节和 8 个字节的无符号数。
表是由多个无符号数或其他表构成的复合数据结构,习惯以 _info 结尾。整个 Class 文件本质上就是一张表。
@Class文件格式|center
无论无符号数还是表,当需要表示同一类型但数量不定的多数据时,常会采用一个前置容量计数器加若干连续数据项的形式。这一系列连续的某一类型数据称为某一类型的集合。

本文将以此 java 代码生成的 Class 文件为例。

public class TestClass {

    private int m ;

    public int inc(){
        return m+1;
    }

}
cafe babe 0000 0034 0016 0a00 0400 1209
0003 0013 0700 1407 0015 0100 016d 0100
0149 0100 063c 696e 6974 3e01 0003 2829
5601 0004 436f 6465 0100 0f4c 696e 654e
756d 6265 7254 6162 6c65 0100 124c 6f63
616c 5661 7269 6162 6c65 5461 626c 6501
0004 7468 6973 0100 154c 436c 6173 7346
696c 652f 5465 7374 436c 6173 733b 0100
0369 6e63 0100 0328 2949 0100 0a53 6f75
7263 6546 696c 6501 000e 5465 7374 436c
6173 732e 6a61 7661 0c00 0700 080c 0005
0006 0100 1343 6c61 7373 4669 6c65 2f54
6573 7443 6c61 7373 0100 106a 6176 612f
6c61 6e67 2f4f 626a 6563 7400 2100 0300
0400 0000 0100 0200 0500 0600 0000 0200
0100 0700 0800 0100 0900 0000 2f00 0100
0100 0000 052a b700 01b1 0000 0002 000a
0000 0006 0001 0000 0006 000b 0000 000c
0001 0000 0005 000c 000d 0000 0001 000e
000f 0001 0009 0000 0031 0002 0001 0000
0007 2ab4 0002 0460 ac00 0000 0200 0a00
0000 0600 0100 0000 0900 0b00 0000 0c00
0100 0000 0700 0c00 0d00 0000 0100 1000
0000 0200 11

魔数:


cafe babe

每个 Class 文件头 4 字节称为魔数(magic_number),用于确定这个文件是否为一个 Class 文件,具有固定的值:0xCAFEBABE(caffe baby~)

版本号:


cafe babe 0000 0034

接下来的 4 字节存储 Class 的版本号。
前 2 字节 0x0000 代表次版本号(minor_version),后 2 字节代表主版本号(major_version),0x0034 即十进制 52,代表 JDK 版本为 1.8。

常量池:


0016
常量池可以理解为 Class 文件的「 资源仓库 」。 由于常量池的常量数量不是固定的,因此在常量池的入口处,即主版本号后两个字符代表常量池容量计数值(constant_pool_count)。 常量池容量计数从 1 开始,第 0 号常量用于在特定情况下需要表达 “ 不引用任何一个常量池项目 ”。这里常量池容量为 0x0016,代表共有 21 个常量。常量池主要存放两种常量: 字面量符号引用。 字面量即字符串、声明为 final 的常量值等。 符号引用包括三类常量:

类和接口的全限定名

字段名称和描述符

方法名称和描述符

常量池中的每一个常量都是一个表,共有 14 种各不相同的表结构。每个表第一位均为 u1 类型的标志位,代表当前常量的类型:

类型标志描述
CONSTANT_Utf8_info1UTF-8编码字符串
CONSTANT_Integer_info3int类型字面值
CONSTANT_Float_info4float类型字面值
CONSTANT_Long_info5long类型字面值
CONSTANT_Double_info6double类型字面值
CONSTANT_Class_info7类或接口符号引用
CONSTANT_String_info8String类型字面值
CONSTANT_Fieldref_info9对一个字段的符号引用
CONSTANT_Methodref_info10类中声明方法的符号引用
CONSTANT_InterfaceMethodref_info11接口中声明方法的符号引用
CONSTANT_NameAndType_info12字段或方法的部分符号引用

常量项结构表

常量项目类型描述
CONSTANT_Utf8_infotagu1值为1
lengthu2UTF-8编码的字符串占用的字节数
bytesu1长度为length的UTF-8编码的字符串
CONSTANT_Integer_infotagu1值为3
bytesu4按照高位在前存储的int值
CONSTANT_Float_infotagu1值为4
bytesu4按照高位在前存储的float值
CONSTANT_Long_infotagu1值为5
bytesu8按照高位在前存储的long值
CONSTANT_Double_infotagu1值为6
bytesu8按照高位在前存储的double值
CONSTANT_Class_infotagu1值为7
indexu2指向全限定名常量项的索引
CONSTANT_String_infotagu1值为8
indexu2指向字符串字面量的索引
CONSTANT_Fieldref_infotagu1值为9
indexu2指向声明字段的类或接口描述符CONSTANT_Class_info的索引项
indexu2指向字段名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_Methodref_infotagu1值为10
indexu2指向声明方法的类描述符CONSTANT_Class_info的索引项
indexu2指向方法名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_InterfaceMethodref_infotagu1值为11
indexu2指向声明方法的接口描述符CONSTANT_Class_info的索引项
indexu2指向方法名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_NameAndType_infotagu1值为12
indexu2指向字段或方法名称常量项目的索引
indexu2指向该字段或方法描述符常量项的索引
0a00 0400 12

第 1 项常量标志位 0x0a ,即十进制 10,对应 CONSTANT_Methodref_info,代表方法的符号引用。
第 1 个 index —— 0x0004 为 CONSTANT_Utf8_info 类型常量,代表此方法所在类的全限定名,指向常量池的第 4 个常量。第 2 个 index —— 0x0012 为 CONSTANT_NameAndType_info 类型,指向第 18 个常量。

09 0003 0013

第 2 项常数标志位 0x09,代表 CONSTANT_Fieldref_info,即字段的符号引用。

0700 14

第 3 项常数标志位 0x07,代表 CONSTANT_Class_info 。 index 为类的全限定名,指向第 20 个常量。

07 0015

第 4 项常数标志位 0x07,index 指向第 21 个常量。

0100 016d

第 5 项常数标志位 0x01,代表 CONSTANT_Utf8_info 。length 为 0x0001,即 1 字节。 bytes 为长度 1 字节的字符串:0x6d,对应ASCII码的 ‘ m ’ 。

0100 0149

同理,第 6 项常数为 ‘ I ’。

0100 063c 696e 6974 3e

第 7 项——“ < init > ”

01 0003 2829 56

第 8 项——“ ()V ”

01 0004 436f 6465

第 9 项——“ Code ”

0100 0f4c 696e 654e 756d 6265 7254 6162 6c65

第 10 项——“ LineNumberTable ”

0100 124c 6f63 616c 5661 7269 6162 6c65 5461 626c 65

第 11 项——“ LocalVariableTable ”

01 0004 7468 6973

第 12 项——“ this ”

0100 154c 436c 6173 7346 696c 652f 5465 7374 436c 6173 733b

第 13 项——“ LClassFile/TestClass; ”

0100 0369 6e63

第 14 项——“ inc ”

0100 0328 2949

第 15 项——“ ()I ”

0100 0a53 6f75 7263 6546 696c 65

第 16 项——“ SourceFile ”

01 000e 5465 7374 436c 6173 732e 6a61 7661

第 17 项——” TestClass.java “

0c00 0700 08

第 18 项标志位对应 CONSTANT_NameAndType_info。
第 1 个 index 指向第 7 个常数,第 2 个 index 指向第 8 个常数。即 “ < init >:()V ”

0c 0005 0006

第 19 项——“ m:I ”

0100 1343 6c61 7373 4669 6c65 2f54 6573 7443 6c61 7373

第 20 项——“ ClassFile/TestClass ”

0100 106a 6176 612f 6c61 6e67 2f4f 626a 6563 74

第 21 项——“ java/lang/Object ”

当然,每次直接分析 Class 文件效率太低了~,java 提供了专门用于分析 Class 文件的工具 javap
@javap|center

访问标志:


常量池结束后,接下来的 2 个字节代表访问标志( access_flags )。用于识别一些类或接口的「访问信息 」。
包括 Class 是类还是接口,是否为 public,是否定义为 abstract 类型,是否声明为 final 等。

标志名标志值标志含义针对的对象
ACC_PUBLIC0x0001public类型所有类型
ACC_FINAL0x0010final类型
ACC_SUPER0x0020使用新的invokespecial语义类和接口
ACC_INTERFACE0x0200接口类型接口
ACC_ABSTRACT0x0400抽象类型类和接口
ACC_SYNTHETIC0x1000该类不由用户代码生成所有类型
ACC_ANNOTATION0x2000注解类型注解
ACC_ENUM0x4000枚举类型枚举
00 21

在本例中 ACC_PUBLIC,ACC_SUPER 为真。
将标志为真的 flag 进行掩码实现:

0x0020|0x0001 = 0x0021

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


Class 文件中由这三项数据确定这个类的「 继承关系 」。类索引为这个类的全限定名,父类索引为这个类的父类,除 Object 外,所有类的父类索引不能为0。
类索引、父类索引均是 u2 类型的数据,接口索引集合为一组 u2 类型的集合。入口第一项 u2 类型数据为接口计数器。

00 0300 0400 00

0x00030x00040x0000 对应常量池的第 3 项与第 4 项,接口为空。

字段表集合


字段表(field_info)集合用于描述接口或者类中声明的「 变量 」,包括类级变量以及实例级变量,但不包括方法内部声明的局部变量。
一个字段使用一个字段表描述。字段表集合的入口 2 个字符同样代表容量计数值。

字段表格式:

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count

字段修饰符放在 access_flags 中,与类中的 access_flags 十分相似:

标志名标志值标志含义
ACC_PUBLIC0x0001字段是否为public
ACC_PRIVATE0x0002字段是否为private
ACC_PROTECTED0x0004字段是否为protected
ACC_STATIC0x0008字段是否为static
ACC_FINAL0x0010字段是否为final
ACC_VOLATILE0x0040字段是否为volatile
ACC_TRANSIENT0x0080字段是否为transient
ACC_SYNTHETIC0x1000字段是否为编译器自动产生
ACC_ENUM0x4000字段是否为enum

接下来是两项索引值:name_index、descriptor_index,代表字段的简单名称和方法的描述符。

描述符含义
B基本数据类型byte
C基本数据类型char
D基本数据类型double
F基本数据类型float
I基本数据类型int
J基本数据类型long
S基本数据类型short
Z基本数据类型boolean
V基本数据类型void
L对象类型
00 0100 0200 0500 0600 00

示例类中字段共 0x0001 个。access_flags 值为 0x0002,代表为 private。name_index为‘ m ’。descriptor_index 为‘ I ’。attributes_count 为 0 。即“privat int m;”

方法表集合


Class 文件对方法的描述与对字段的描述十分相似。方法表的结构与字段表结构完全相同~

类型名称数量
u2access_flags1
u2name_index1
u2descriptor_index1
u2attributes_count1
attribute_infoattributesattributes_count
标志名标志值标志含义
ACC_PUBLIC0x0001方法是否为public
ACC_PRIVATE0x0002方法是否为private
ACC_PROTECTED0x0004方法是否为protected
ACC_STATIC0x0008方法是否为static
ACC_FINAL0x0010方法是否为final
ACC_SYHCHRONRIZED0x0020方法是否为synchronized
ACC_BRIDGE0x0040方法是否为编译器产生的桥接方法
ACC_VARARGS0x0080方法是否接受不定参数
ACC_NATIVE0x0100方法是否为native
ACC_ABSTRACT0x0400方法是否为abstract
ACC_STRICTFP0x0800方法是否为strictfp
ACC_SYNTHETIC0x1000方法是否为编译器自动产生

方法中的代码经编译器译为字节码指令后,存放在方法属性表集合的 Code 属性中。

00 0200 0100 0700 0800 0100 09

0x0002 代表集合中共有两个方法,分别为编译器添加的实例构造器 < init > 和 inc() 方法。
以第一个方法为例: ACC_PUBLIC 标志为真,名称索引为 0x0007。描述符索引为 0x0008。属性表计数器为 1。属性名称索引为 0x0009 —— Code,说明此属性为方法的字节码描述,方法中的 java 代码,经编译器编译为字节码文件后,就存放在 Code 属性中。

属性表集合


用于描述某些场景「 专有信息 」,在 Class 文件、字段表、方法表中均可携带自己的属性表。属性表中不要求严格顺序,但一个符合规则的属性表应满足如下结构:

类型名称数量
u2attribute_name_index1
u2attribute_length1
u1infoattribute_length

虚拟机预定义属性:

属性名称使用位置含义
Code方法表Java代码编译成的字节码指令
ConstantValue字段表final关键字定义的常量池
Deprecated类,方法,字段表被声明为deprecated的方法和字段
Exceptions方法表方法抛出的异常
EnclosingMethod类文件仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass类文件内部类列表
LineNumberTableCode属性Java源码的行号与字节码指令的对应关系
LocalVariableTableCode属性方法的局部便狼描述
StackMapTableCode属性JDK1.6中新增的属性,供新的类型检查检验器检查和处理目标方法的局部变量和操作数有所需要的类是否匹配
Signature类,方法表,字段表用于支持泛型情况下的方法签名
SourceFile类文件记录源文件名称
SourceDebugExtension类文件用于存储额外的调试信息
Synthetic类,方法表,字段表标志方法或字段为编译器自动生成的
LocalVariableTypeTable使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations类,方法表,字段表为动态注解提供支持
RuntimeInvisibleAnnotations表,方法表,字段表用于指明哪些注解是运行时不可见的
RuntimeVisibleParameterAnnotation方法表作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法
RuntimeInvisibleParameterAnnotation方法表作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数
AnnotationDefault方法表用于记录注解类元素的默认值
BootstrapMethods类文件用于保存invokeddynamic指令引用的引导方式限定符

这里以 Code 属性为例:

类型名称数量
u2attribute_name_index1
u4attribute_length1
u2max_stack1
u2max_locals1
u4code_length1
u1codecode_length
u2exception_table_length1
exception_infoexception_tableexception_length
u2attributes_count1
attribute_infoattributesattributes_count
00 0100 0100 0000 052a b700 01b1

0x0001 代表操作数栈深度max_stack为 1。
0x0001 代表局部变量表所需深度max_locals为 1。
0x0005 代表字节码区域所占空间,依次读取 5 个字节,并按照字节码指令表翻译字节码指令。
0x2a —— aload_0,将第 0 个 Slot 中的 reference 类型的本地变量推送至操作数栈顶
0xb7—— invokespecial,以栈顶 reference 数据指向的对象作为方法接收者,调用此对象的实例构造方法,private 方法,或者其他父类方法。此方法存在 u2 类型参数说明调用那个方法。0x000a 代表常量池 < init >。
0xb1 ——对应指令为 return 返回此方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值