1.Class文件结构
jvm规范规定,Class文件采取一种类似C语言结构体的伪结构来存储数据,这种数据结构只有两种数据类型:无符号数和表。
- 无符号数属于基本数据类型,以u1,u2,u4,u8分别代表1个字节、2个字节、4个字节和8个无符号数。无符号可以描述数字、索引、引用、数量值或者按照utf-8编码构成的字符串值。
- 表是有无符号数和其他表作为数据项的复合数据类型,所有表都以_info结尾。
1.1 魔数与Class版本
- 每个Class文件都以4个字节的魔数开头,它唯一的作用就是确定该class文件是否能被jvm接受。值为:0xCAFEBABE。
- 紧接着是4个字节的魔数是声明class版本号的。
1.2 常量池
常量池是一个class的资源仓库。常量池入口需放置一个u2的常量池容量计数(从1开始,0代表不引用任何常量)。
常量池有两大类:
- 文本字符串和声明是final的常量值
- 编译方面的概念:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。
java编译没有连接这一步,无法生成字段和方法的内存布局。jvm运行时,需要从常量池获得对应的符号引用,在类创建时或运行时、解析、翻译到具体的内存地址中。
1.3 访问标识
2个字节,标识类或接口的层次的范文信息,包含这个class是类还是接口;是否是public 是否是abstract,是否声明为final。
1.4 类索引、父类索引与接口索引集合
类索引和父类索引都是u2类型,而接口索引集合是一组u2类型的集合(当然在这之前有一个u2类型的计数)。
- 对于类索引和父类索引u2类型的索引值只想一个类型为CONSTANT_Class_info的类描述符,通过CONSTANT_Class_info类型对的索引值可以找到定义在CONSTANT_utf8_info类型的常量中的全限定名字符串。
- 对于接口索引集合,入口的一个u2接口计数,没有实现任何接口为0,后面的接口表不占用任何字节。
1.5 字段表集合
字段表用来描述接口或类中的成员变量,但不包括局部变量。字段表结构如下:
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flag | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_counts | 1 |
u2 | attributes | attibutes_count |
访问标识
标识名称 | 标识值 | 含义 |
---|---|---|
ACC_PUBLIC | 0X0001 | 是否为public |
ACC_PRIVATE | 0X0002 | 是否为private |
ACC_PROTECTED | 0X0004 | 是否为protected |
ACC_STATIC | 0X0008 | 是否为static |
ACC_FINAL | 0X0010 | 是否为final |
ACC_VOLATILE | 0X0040 | 是否为volatile |
ACC_TRANSIENT | 0X0080 | 是否为transient |
ACC_SYNITHETIC | 0X1000 | 是否由编译器自动生成 |
ACC_ENUM | 0X4000 | 是否为enum |
name_index和descriptor_index 分别代表字段的简单明和描述索引。
全限定名:就是将类的全类名中的.换成/,如org/fenixsoft/calss/TestClass。
简单名:没有类型和参数修饰的方法或字段。
描述符:1.描述字段的类型;2.方法的参数列表(数量、类型和顺序)和返回值。对于基本类型返回值和空都用大写字母表示;而对象用全限定名表示;对于数组则在原有的标识符前置[(多维数组就多个)。用描述符描述方法时:先参数列表,并按顺序放在一个()内,然后返回值。如方法void inc() 描述符为()V。
属性表集合:用来描述存储一些额外的信息,字段都可以在描述0-n个额外信息。如字典声明为static final 会产生ConstantValue属性。
字段表结合不会描述从父类中继承的字段,但由可能列出原本java代码中不存在的字段,譬如在内部勒种为了保持对外部类的访问性。
1.6 方法表集合
方法表结构和字段表一样。
类型 | 名称 | 数量 |
---|---|---|
u2 | access_flag | 1 |
u2 | name_index | 1 |
u2 | descriptor_index | 1 |
u2 | attributes_counts | 1 |
u2 | attributes | attibutes_count |
访问标识与方法不同,少了volatile和transient
标识名称 | 标识值 | 含义 |
---|---|---|
ACC_PUBLIC | 0X0001 | 是否为public |
ACC_PRIVATE | 0X0002 | 是否为private |
ACC_PROTECTED | 0X0004 | 是否为protected |
ACC_STATIC | 0X0008 | 是否为static |
ACC_FINAL | 0X0010 | 是否为final |
ACC_SYNCHORINIZED | 0X0020 | 是否为synchronized |
ACC_BRIDGE | 0X0040 | 是否为编译器产生的桥接方法 |
ACC_VARARGS | 0X0080 | 是否接受不定参数 |
ACC_NATIVE | 0X0100 | 是否为native方法 |
ACC_ABSTRACT | 0X0400 | 是否为abstract方法 |
ACC_STRICTFFP | 0X0800 | 是否为strickfp |
ACC_SYNTHETIC | 0X1000 | 是否为编译器生成 |
特别注意:方法里的代码存储在方法属性集合中一个名为Code的属性里面。
如果父类的方法没有被子类覆盖,方法表集合不会有父类的任何信息。但可能有编译器自动添加方法。如clinit和init方法。
1.7 属性表集合
class文件、字段表、方法表都可以存在属性表。
属性表不要求严格的顺序,不重名。任何人实现的jvm都可以写入自己定义的属性信息。
对于每个属性他的名字需要从常量池中引用一个constant_utf8_info常量表示。
属性表j基本结构
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u1 | info | attribute_length |
属性名 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | java代码编译生成的字节码指令 |
ConstantValue | 字段表 | final关键字定了的常量 |
Deprecated | 方法表 | deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 晋档一个类为局部类或匿名内部类才拥有这个属性,这个属性拥有标明这个歌类所在的外围方法 |
1.7.1 Code属性
并非所有方法表都拥有code属性表,比如接口和abstract方法。
1.7.2 Excetions属性
列举出方法可能抛出的受检异常。
1.7.3 LineNumberTable属性
拥有描述java源码和class字节码行号之间的对应关系。javac 分别使用 -g:none或-g:lines 取消和要求生成这项信息。
1.7.4 LocalVariableTable属性
LocalVariableTable属性用于描述栈帧中局部变量表与java源码定义变量的关系。javac中 -g:none和-g:vars选项取消或生成。
1.7.5 SourceFile属性
SourceFile用于标记生成Class文件的源码名称。
1.7.6 ConstantValue属性
HotSpot认为 final static 更符合常量语义,。
1.7.7 InnerClass属性
该属性用来记录内部类和宿主类的福暗恋。
1.7.8 Deprecated和Synthetic
1.7.9 StackMapTable属性
复杂的变化属性,位于Code属性中。省略在运行期间通过数据流分析去却字节码的行为逻辑合法性的步骤,二是在编译阶段将一系列的验证类型直接记录在class文件中,提升字节码验证性能
1.7.10 Signature属性
Signature属性会为它记录泛型签名和信息,java语言是擦除实现的伪泛型。java的反射API能够获取泛型类型,最终的数据来源就是这个属性。
1.7.11 BootstrapMethods属性
参考
- https://www.jianshu.com/p/247e2475fc3a
- 周志明,深入理解Java虚拟机:JVM高级特性与最佳实践,机械工业出版社
- https://www.cnblogs.com/paddix/p/5282004.html