- 引用
- 类型
无符号数
u1、u2、u4、u8
分别代表1、2、4、8
个字节的无符号数表
表由多个无符号数或其他表构成,一般以_info
结尾
- 结构说明
图
- 如何表示同一类型但数量不定的多个数据
一般用一个前置_count
的u2
类型+若干个数据的形式,如图
- 具体结构
magic
称为魔数,唯一作用是判断该文件是否为一个能被jvm接受的class文件,固定值为
0xCAFEBABE
-
major_version
和minor_version
主版本号和次版本号,高版本的jdk能向下兼容低版本的class文件,但不能运行更高版本的class文件
constant_pool
- 说明
常量池是class文件中与其他项目关联最多的数据类型- 2类常量
字面量、符号引用
字面量接近java层面的常量,比如字符串、final常量- 符号引用
包括三类:
类和接口的全限定名(即带有包名的Class名,如:org.lxh.test.TestClass)
字段的名称和描述符(private、static等描述符)
方法的名称和描述符(private、static等描述符)
当虚拟机运行时,需要从常量池中获取对应的符号引用,再在类加载过程中的解析阶段将符号引用
替换为直接引用
,并翻译到具体的内存池中。即虚拟机在加载Class文件时才会进行动态连接,也就是说,Class文件中不会保存各个方法和字段的最终内存布局信息,因此,这些字段和方法的符号引用不经过转换是无法直接被虚拟机使用的。
- 符号引用和直接引用的区别与关联
符号引用
:符号引用以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可。符号引用与虚拟机实现的内存布局无关,引用的目标并不一定已经加载到了内存中。
直接引用
:直接引用可以是直接指向目标的指针、相对偏移量或是一个能间接定位到目标的句柄。直接引用是与虚拟机实现的内存布局相关的,同一个符号引用在不同虚拟机实例上翻译出来的直接引用一般不会相同。如果有了直接引用,那说明引用的目标必定已经存在于内存之中了。其实直接引用就相当于一个内存地址
--
- 常量池的具体结构
常量池中每一个项常量都是一个表,在jdk1.7之前共有11种结构不同,每种表的第一位是标志位,类型
u1
,代表该表对应常量类型
CONSTANT_Class_info
这个表中有一个
u2
类型的index
,它保存了一个索引值,指向常量池中一个CONSTANT_Utf8_info
,该常量中保存了该类的全限定名字符串(如org.lxh.test.TestClass
)
CONSTANT_Fieldref_info、CONSTANT_Methodref_info、CONSTANT_InterfaceMethodref_info
这3个表中都有第一个
index
属性,存放该字段或方法所属的类或接口的索引值。
第二个index是
CONSTANT_Utf8_info
tag
是类型
length
是字符串的字节数
bytes
是个不定长的单位为1个字节的字符串
保存诸如Class名、字段名、方法名、修饰符(如private、public)等字符串,因此java中方法名、字段名、类名字段的最大长度就是length
的最大值,即2个字节
的最大值65535
,也就是bytes
的个数最多等于64KB-1
access_flag
2个字节,代表访问标志,用于识别一些类或接口层次的访问信息,包括
是类或接口
、是否public、abstract
、如果是类的话,是否final
,每种访问信息都是由1个16进制表示,如果有多重访问信息,那么总的标志值是这几个访问信息的逻辑或
this_class、super_class、interfaces
类索引(
this_class
)和父类索引(
super_class)都是u2类型,接口索引是一组u2类型,class文件中由这3项数据来确定这个类的继承关系。
类索引和父类索引指向类型为
CONSTANT_Class_info的类描述符常量,据此找到类的全限定名(如
org.lxh.test.TestClass),而接口索引集合的每个元素同样指向
CONSTANT_Class_info`的接口描述符常量,就用来描述这个类实现了哪些接口,这些被实现的接口将按implements语句(如果这个类本身是个接口,则应当是extend语句)后的接口顺序从左到右排列在接口的索引集合中。
fields
字段表
field_info
用用户描述接口或类中声明的标量。字段包括类变量或实例级变量,但不包括局部变量(方法内声明的变量)。字段的名字
、数据类型
、修饰符
等都是无法固定
的,只能引用常量池中的常量来描述。
下面是字段表的格式
其中access_flags
与类中的access_flags
相似,是表示数据类型的修饰符,如public、static、volatile等。后面的name_index
和descriptor_index
都是对常量池的索引,分别代表字段的简单名称
及字段和方法的描述符
。
attribute_info
属性表集合用于存储一些额外的信息。比如,如果在类中有如下字段的声明:staticfinalint m = 2;那就可能会存在一项名为ConstantValue的属性,它指向常量2
最后需要注意一点:字段表集合中不会列出从父类或接口中继承而来的字段,但有可能列出原本Java代码中不存在的字段。比如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
-
简单名称
、描述符
和全限定名
全限定名:如
org.lxh.test包下的TestClass类
的全限定名为org/lxh/test/TestClass
,即把包名中的“.”改为“/”,为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“,”来表示全限定名结束。
简单名称:则是指没有类型或参数
修饰的方法或字段名称
,如果一个类中有这样一个方法boolean get(int name)
和一个变量private final static int m
,则他们的简单名称则分别为get()
和m
。
描述符:而描述符的作用则是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序等)和返回值的。根据描述符规则,详细的描述符标示字的含义如下表所示:
数组类型:每一维度将使用一个前置[
来描述,如int[][]
将记录为[[I
,String[]
为[Ljava/lang/String
。
方法描述符:按照县参数后返回值的顺序描述,按照先参数后返回值的顺序描述,参数要按照严格的顺序放在一组小括号内,如int getIndex(String name,char[] tgc,int start,int end,char target)
的描述符为(Ljava/lang/String[CIIC)I
- methods
方法表
与,方法里的代码,经过编译成字节码指令后,存放在方法属性表集合中一个field_info
中的属性表结构相同Code
属性中。与字段表集合相对应,如果父类方法在子类中没有被重写
,方法表集合中就不会出现
来自父类的方法信息。但同样,有可能会出现由编译器自动添加的方法,最典型的的类构造器<clinit>
方法和实例构造器<init>
方法。
重载:java中,重载
一个方法,除了要与原方法具有相同的简单名称外,还要拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不包含在特征签名中,所以java无法仅靠返回值不同来对一个方法进行重载。
--
- attributes
属性表在class文件,字段表、方法表都出现过,属性表用于描述某些场景专有的信息。
属性表集合限制不严格
,不要求各个属性表具有严格的顺序
,并且只要不与已有的属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,但Java虚拟机运行时会忽略掉它不认识的属性。
Java虚拟机规范中预定义了9项虚拟机应当能识别的属性(JDK1.5后又增加了一些新的特性,因此不止下面9项,但下面9项是最基本也是必要,出现频率最高的),如下表所示:
对于每个属性,它的名称都是从常量池中引用一个CONSTANT_Utf8_info
,每个属性值的结构可以自定义,只需说明属性值的位数长度。个符合规则的属性表至少应具有“attribute_name_index”、“attribute_length”和至少一项信息属性。
Code属性
Java程序方法体中的代码经过Javac编译后,生成的字节码指令便会存储在Code属性中,但并非所有的方法表都必须存在这个属性,比如接口或抽象类中的方法就
不存在Code属性
。如果方法表有Code属性存在,那么它的结构将如下表所示:
max_stack
是操作数栈深度的最大值,max_locals
代表局部变量表所需的存储空间,单位是Slot
,并不是在方法中用到了多少局部变量,就把这些局部变量所占Slot之和作为max_locals的值,原因是局部变量中的Slot
可以重用。