- Class文件字节编码存储顺序统一
- 无符号数和表
- class文件中的字节流
- Class文件中的字节流每项的含义
- 常量池结构
- CONSTANT_Class_info结构
- CONSTANT_Fieldref_info结构
- CONSTANT_Methodref_info结构
- CONSTANT_InterfaceMethodref_info结构
- CONSTANT_String_info结构
- CONSTANT_Integer_info结构
- CONSTANT_Float_info结构
- CONSTANT_Long_info结构
- CONSTANT_Double_info结构
- CONSTANT_NameAndType_info 结构
- CONSTANT_Utf8_info结构
- CONSTANT_MethodHandle_info 结构
- CONSTANT_MethodType_info 结构
- CONSTANT_InvokeDynamic_info 结构
- 属性表集合(attributes_count和attribute)
学习Java的朋友应该都知道Java从刚开始的时候就打着平台无关性的旗号,说“一次编写,到处运行”,其实说到无关性,Java平台还有另外一个无关性那就是语言无关性,要实现语言无关性,那么Java体系中的class的文件结构或者说是字节码就显得相当重要了,其实Java从刚开始的时候就有两套规范,一个是Java语言规范,另外一个是Java虚拟机规范,Java语言规范只是规定了Java语言相关的约束以及规则,而虚拟机规范则才是真正从跨平台的角度去设计的。今天我们就以一个实际的例子来看看,到底Java中一个Class文件对应的字节码应该是什么样子。 这篇文章将首先总体上阐述一下Class到底由哪些内容构成,然后再用一个实际的Java类入手去分析class的文件结构。
Class文件字节编码存储顺序统一
Class文件是有8位为基础的字节流构成的,这些字节流之间都严格按照规定的顺序排列,并且字节之间不存在任何空隙,对于超过8位的数据,将按照Big-Endian的顺序存储的,也就是说高位字节存储在低的地址上面,而低位字节存储到高地址上面,其实这也是class文件要跨平台的关键,因为PowerPC架构的处理采用Big-Endian的存储顺序,而x86系列的处理器则采用Little-Endian的存储顺序,因此为了Class文件在各中处理器架构下保持统一的存储顺序,虚拟机规范必须对起进行统一。
无符号数和表
Class文件结构采用类似C语言的结构体来存储数据的,主要有两类数据项,无符号数和表。无符号数用来表述数字,索引引用以及字符串等,比如u1
,u2
,u4
,u8
分别代表1个字节,2个字节,4个字节,8个字节的无符号数,而表是有多个无符号数以及其它的表组成的复合结构。可能大家看到这里对无符号数和表到底是上面也不是很清楚,不过不要紧,等下面实例的时候,我会再以实例来解释。
class文件中的字节流
Class文件结构。
图来自The Java Virtual Machine Specification Java SE 7 Edition
比如cp_info
,cp_info
表示常量池,上图中用constant_pool[constant_pool_count-1]
的方式来表示常量池有constant_pool_count-1
个常量,它这里是采用数组的表现形式,但是大家不要误以为所有的常量池的常量长度都是一样的,其实这个地方只是为了方便描述采用了数组的方式,但是这里并不像编程语言那里,一个int
型的数组,每个int
长度都一样。
Class文件中的字节流每项的含义
每一项都具体代表含义。
u4 magic
表示魔数,并且魔数占用了4个字节,魔数到底是做什么的呢?它其实就是表示一下这个文件的类型是一个Class文件,而不是一张JPG图片,或者AVI的电影。而Class文件对应的魔数是0xCAFEBABE
。
u2 minor_version
表示Class文件的次版本号,并且此版本号是u2
类型的无符号数表示。
u2 major_version
表示Class文件的主版本号,并且主版本号是u2
类型的无符号数表示。major_version
和minor_version
主要用来表示当前的虚拟机是否接受当前这种版本的Class文件。不同版本的Java编译器编译的Class文件对应的版本是不一样的。高版本的虚拟机支持低版本的编译器编译的Class文件结构。比如Java SE 6.0对应的虚拟机支持Java SE 5.0的编译器编译的Class文件结构,反之则不行。
u2 constant_pool_count
常量池的数量。
cp_info
表示常量池,这里面就存储了各种各样的字面量和符号引用。
放到常量池的中数据项在The Java Virtual Machine Specification Java SE 7 Edition
中一共有14个常量,每一种常量都是一个表,并且每种常量都用一个公共的部分tag(标识符)来表示是哪种类型的常量。
这里我们需要重点来说一下常量池是什么东西,请大家不要与JVM内存模型中的运行时常量池混淆了,Class文件中常量池主要存储了字面量以及符号引用。
字面量
包括字符串,final常量的值或者某个属性的初始值等等。
符号引用
存储类和接口的全限定名称,字段的名称以及描述符,方法的名称以及描述符。
这里名称可能大家都容易理解,至于描述符的概念,放到下面说字段表以及方法表的时候再说。另外大家都知道JVM的内存模型中有堆、栈、方法区、程序计数器构成,而方法区中又存在一块区域叫运行时常量池,运行时常量池中存放的东西其实也就是编译器产生的各种字面量以及符号引用,只不过运行时常量池具有动态性,它可以在运行的时候向其中增加其它的常量进去,最具代表性的就是String
的intern()
。
14个常量
名字 | tag(标识位) | 说明 |
---|---|---|
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_InterfaceMethodref_info | 11 | 接口中方法的符号引用 |
CONSTANT_NameAndType_info | 12 | 字段和方法的名称以及类型的符号引用 |
u2 access_flags
类或者接口的访问信息(权限),如下图。
u2 this_class
类的常量池索引,指向常量池中CONSTANT_Class_info
的常量。
u2 super_class
超类的索引,指向常量池中CONSTANT_Class_info
的常量。
u2 interface_counts
接口的数量
u2 interface[interface_counts]
接口表,它里面每一项都指向常量池中CONSTANT_Class_info
常量。
u2 fields_count
类的实例变量和类变量的数量。
field_info fields[fields_count]
字段表的信息,其中字段表的属性结构如下图。
access_flags
字段的访问标示,比如字段是public
,private
,protect
等。
name_index
字段名称,指向常量池中类型是CONSTANT_UTF8_info
的常量。
descriptor_index
字段的描述符,它也指向常量池中类型为CONSTANT_UTF8_info
的常量。
attributes_count
字段表中的属性表的数量,而属性表是则是一种用与描述字段,方法以及类的属性的可扩展的结构,不同版本的Java虚拟机所支持的属性表的数量是不同的。
u2 methods_count
方法表的数量。
method_info
方法表,方法表的具体结构如下图。
access_flags
方法的访问标示。
name_index
名称的索引。
descriptor_index
方法的描述符。
attribute_info
类似字段表中的属性表,只不过字段表和方法表中属性表中的属性是不同的,比如方法表中就Code属性,表示方法的代码,而字段表中就没有Code
属性。其中具体Class中到底有多少种属性,等到Class文件结构中的属性表的时候再说说。
attribute_count
属性表的数量,说到属性表,需要明确以下2点。
- 属性表存在于Class文件结构的最后,字段表,方法表以及
Code
属性中,也就是说属性表中也可以存在属性表。 - 属性表的长度是不固定的,不同的属性,属性表的长度是不同的。
常量池结构
CONSTANT_Class_info结构
CONSTANT_Class_info
结构用于表示类或接口,格式如下。1
2
3
4CONSTANT_Class_info {
u1 tag;
u2 name_index;
}
tag
CONSTANT_Class_info
结构的tag
项的值为CONSTANT_Class(7)
name_index
值,必须是对常量池的一个有效索引。 常量池在该索引处的项必须是CONSTANT_Utf8_info
结构, 代表一个有效的类或接口二进制名称的内部形式。
CONSTANT_Fieldref_info结构
1 | CONSTANT_Fieldref_info { |
tag
值是CONSTANT_Fieldref(9)
class_index
值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info
结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。
类型既可以是类也可以是接口。
name_and_type_index
值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info
结构,它表示当前字段或方法的名字和描述符。
给定的描述符必须是字段描述符。
CONSTANT_Methodref_info结构
1 | CONSTANT_Methodref_info { |
tag
值是CONSTANT_Methodref(10)
class_index
值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info
结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。
类型必须是类(不能是接口)。
name_and_type_index
值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_NameAndType_info
结构,它表示当前字段或方法的名字和描述符。
给定的描述符必须是方法描述符。
如果一个CONSTANT_Methodref_info
结构的方法名以<`
(‘\u003c’)开头,则说明这个方法名是特殊的
CONSTANT_InterfaceMethodref_info结构
1 | CONSTANT_InterfaceMethodref_info { |
tag
值是CONSTANT_InterfaceMethodref(11)
class_index
值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info
结构,表示一个类或接口,当前字段或方法是这个类或接口的成员。
类型必须是接口(不能是类)。
name_and_type_index
值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_NameAndType_info
结构,它表示当前字段或方法的名字和描述符。
给定的描述符必须是方法描述符。
CONSTANT_String_info结构
CONSTANT_String_info
用于表示java.lang.String
类型的常量对象。1
2
3
4CONSTANT_String_info {
u1 tag;
u2 string_index;
}
tag
值是CONSTANT_String(8)
。
string_index
string_index
项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_Utf8_info
结构,表示一组Unicode
码点序列,这组Unicode
码点序列最终会被初始化为一个String
对象。
CONSTANT_Integer_info结构
CONSTANT_Intrger_info
结构表示4字节(int
和float
)的数值常量。1
2
3
4CONSTANT_Integer_info {
u1 tag;
u4 bytes;
}
tag
值是CONSTANT_Integer(3)
。
bytes
int
常量的值,按照Big-Endian的顺序存储。
CONSTANT_Float_info结构
1 | CONSTANT_Float_info { |
tag
值是CONSTANT_Float(4)
。
bytes
bytes
项按照IEEE 754
单精度浮点格式。表示float
常量的值,按照Big-Endian的顺序存储。CONSTANT_Float_info
结构表示的值将按照下列方式来表示,bytes
项的值首先被转换成一个 int
常量的bits
。
- 如果
bits
值为0x7f800000
,表示float
值为正无穷。 - 如果
bits
值为0xff800000
,表示float
值为负无穷。 - 如果
bits
值在范围0x7f800001
到0x7fffffff
或者0xff800001
到0xffffffff
内,表示float值为NaN
。
在其它情况下,设 s、 e、 m,它们值根据bits
和如下公式计算。float
的浮点值为数值表达式 s·m·2e–150
的计算结果。1
2
3
4
5int s =((bits >> 31) == 0) ? 1 : -1;
int e =((bits >> 23) & 0xff);
int m =(e == 0) ?
bits & 0x7fffff) << 1 :
(bits & 0x7fffff) | 0x800000;
CONSTANT_Long_info结构
CONSTANT_Long_info
结构表示8字节(long
和double
)的数值常量。1
2
3
4
5CONSTANT_Long_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
在Class文件的常量池中,所有的8字节的常量都占两个表成员(项)的空间。CONSTANT_Long_info
结构的项在常量池中的索引为n,则常量池中下一个有效的项的索引为n+2
,此时常量池中索引为n+1
的项有效但必须被认为不可用。
tag
值是CONSTANT_Long(5)
。
high_bytes 和 low_bytes
无符号的 high_bytes
和 low_bytes
项用于共同表示 long
型常量,构造形式为((long) high_bytes << 32) + low_bytes,high_bytes
和 low_bytes
都按照 Big-Endian顺序存储。
CONSTANT_Double_info结构
CONSTANT_Double_info
结构表示8字节(long
和double
)的数值常量。1
2
3
4
5CONSTANT_Double_info {
u1 tag;
u4 high_bytes;
u4 low_bytes;
}
在Class 文件的常量池中,所有的8字节的常量都占两个表成员(项)的空间。 CONSTANT_Double_info
结构的项在常量池中的索引为n,则常量池中下一个有效的项的索引为n+2
, 此时常量池中索引为n+1
的项有效但必须被认为不可用。
tag
值是 CONSTANT_Double(6)
。
high_bytes 和 low_bytes
high_bytes
和low_bytes
共同按照IEEE 754
双精度浮点格式表示double
常量的值。high_bytes
和low_bytes
都按照Big-Endian顺序存储。CONSTANT_Double_info
结构表示的值将按照下列方式来表示,high_bytes
和low_bytes
首先被转换成一个long
常量的bits
。
- 如果
bits
值为0x7ff0000000000000L
,表示double
值为正无穷。 - 如果
bits
值为0xfff0000000000000L
,表示double
值为负无穷。 - 如果
bits
值在范围0x7ff0000000000001L
到0x7fffffffffffffffL
或者0xfff0000000000001L
到0xffffffffffffffffL
内,表示double
值为NaN
。
在其它情况下,设s、 e、 m,它们的值根据bits
和如下公式计算。double
的浮点值为数学表达式s·m·2e – 1075
的计算结果。1
2
3
4
5int s =((bits >> 63) == 0) ? 1 : -1;
int e =(int)((bits >> 52) & 0x7ffL);
long m =(e == 0) ?
(bits & 0xfffffffffffffL) << 1 :
(bits & 0xfffffffffffffL) | 0x10000000000000L;
CONSTANT_NameAndType_info 结构
CONSTANT_NameAndType_info
结构没有标识出它所属的类或接口,格式如下。1
2
3
4
5CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
}
tag
值为CONSTANT_NameAndType(12)
。
name_index
name_index
项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_Utf8_info
结构,这个结构要么表示特殊的方法名<init>
,要么表示一个有效的字段或方法的非限定名Unqualified Name
。
descriptor_index
descriptor_index
项的值必须是对常量池的有效索引, 常量池在该索引处的项必须是CONSTANT_Utf8_info
结构,这个结构表示一个有效的字段描述符或方法描述符。
CONSTANT_Utf8_info结构
CONSTANT_Utf8_info
结构用于表示字符串常量的值。1
2
3
4
5CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
tag
值为CONSTANT_Utf8(1)
。
length
bytes[]
数组的长度(注意,不能等同于当前结构所表示的String
对象的长度), CONSTANT_Utf8_info
结构中的内容是以length
属性确定长度而不是以null作为字符串的终结符。
bytes[]
bytes[]
是表示字符串值的byte
数组,bytes[]
数组中每个成员的byte
值都不会是0,也不在0xf0
至0xff
范围内。字符串常量采用改进过的UTF-8编码表示。 这种以改进过的UTF-8编码中,用于表示的字符串的码点字符序列可以包含ASCII中的所有非空(Non-Null
)字符和所有 Unicode
编码的字符,一个字符占一个byte
。(1个英文字符占1个byte
,1个中文字符占2个byte),长度=byte
长度。
码点在范围’\u0001
‘至’\u007F
‘内的字符用一个单字节表示。
| 0 | bits 6-0 |byte
的后7位数据表示一个码点值。
字符为’\u0000
‘(表示字符’null’),或者在范围’\u0080
‘至’\u07FF
‘的字符用一对字节x和y表示。
x:| 1 | 1 | 0 | bits 10-6 |
y:| 1 | 0 | bits 5-0 |
x 和 y 计算字符值的公式:((x & 0x1f) << 6) + (y & 0x3f)
在范围’\u0800
‘至’\uFFFF
‘中的字符用 3 个字节 x, y 和 z 表示。
x:| 1 | 1 | 1 | 0 | bits 15-12 |
y:| 1 | 0 | bits 11-6 |
z:| 1 | 0 | bits 5-0 |
x,y和z计算字符值的公式:((x & 0xf) << 12) + ((y & 0x3f) << 6) + (z & 0x3f)
超过U+FFFF
范围的字符( 称为补充字符,Supplementary Characters),在UTF-16编码中也需要2个UTF-16字符单元来表示,而UTF-16中的每个字符单元占3个字节,这就意味着在我们的编码方式中,补充字符需要6个字节来表示,u,v,w,x,y和z。字符值的公式为0x10000+((v&0x0f)<<16)+((w&0x3f)<<10)+(y&0x0f)<<6)+(z&0x3f)
x:| 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
y:| 1 | 0 | 1 | 1 | bits 9-6 |
z:| 1 | 0 | bits 5-0 |
u:| 1 | 1 | 1 | 0 | 1 | 1 | 0 | 1 |
v:| 1 | 0 | 1 | 0 | (bits 20-16)-1 |
w:| 1 | 0 | bits 15-10 |
在Class文件中,多字节字符按照Big-Endian顺序存储。
和“标准”版UTF-8格式相比, Java虚拟机采用的改进版UTF-8格式有2点不同。
- “null”字符
(char) 0
用双字节格式编码表示而不是单字节,所以,改进版UTF-8格式不会直接出现null值。 - 改进版的UTF-8只使用标准版UTF-8中的单字节、双字节和三字节格式。 Java虚拟机不能识别标准版UTF-8格式定义4字节格式,而是使用自定义的二次三字节(Two-Times-Three-Byte)格式来代替。
更多关于标准版UTF-8格式的内容可以参考《The Unicode Standard》(版本 6.0.0)的第 3.9 章节“Unicode Encoding Forms”。
CONSTANT_MethodHandle_info 结构
CONSTANT_MethodHandle_info
结构用于表示方法句柄1
2
3
4
5CONSTANT_MethodHandle_info {
u1 tag;
u1 reference_kind;
u2 reference_index;
}
tag
值为 CONSTANT_MethodHandle。
reference_kind
值必须在1至9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法句柄的字节码行为(Bytecode Behavior)。
reference_index
值必须是对常量池的有效索引。
reference_kind
项的值为1(REF_getField
)、2(REF_getStatic
)、3(REF_putField
)或4(REF_putStatic
),那么常量池在reference_index
索引处的项必须是CONSTANT_Fieldref_info
(§4.4.2)结构,表示由一个字段创建的方法句柄。reference_kind
项的值是5(REF_invokeVirtual
)、6(REF_invokeStatic
)、7(REF_invokeSpecial
)或8(REF_newInvokeSpecial),那么常量池在reference_index
索引处的项必须是CONSTANT_Methodref_info
(§4.4.2)结构,表示由类的方法或构造函数创建的方法句柄。如果reference_kind
项的值是9(REF_invokeInterface
),那么常量池在reference_index
索引处的项必须是CONSTANT_InterfaceMethodref_info
(§4.4.2)结构,表示由接口方法创建的方法句柄。reference_kind
项的值是5(REF_invokeVirtual
)、6(REF_invokeStatic
)、7(REF_invokeSpecial
)或9(REF_invokeInterface
),那么方法句柄对应的方法不能为实例初始化<init>
方法或类初始化方法<clinit>
。reference_kind
项的值是8(REF_newInvokeSpecial
),那么方法句柄对应的方法必须为实例初始化<init>
方法。CONSTANT_MethodType_info 结构
1
2
3
4CONSTANT_MethodType_info {
u1 tag;
u2 descriptor_index;
}
tag
值为CONSTANT_MethodType(16)
。
descriptor_index
值是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Utf8_info
结构,表示方法的描述符。
CONSTANT_InvokeDynamic_info 结构
CONSTANT_InvokeDynamic_info
用于表示invokedynamic
指令所使用到的引导方法(Bootstrap Method)、引导方法使用到动态调用名称(Dynamic Invocation Name)、 参数和请求返回类型、以及可以选择性的附加被称为静态参数(Static Arguments)的常量序列。1
2
3
4
5CONSTANT_InvokeDynamic_info {
u1 tag;
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
tag
值为CONSTANT_InvokeDynamic(18)。
bootstrap_method_attr_index
值是对当前Class文件中引导方法表的bootstrap_methods[]
数组的有效索引。
name_and_type_index
值是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info
结构,表示方法名和方法描述符。
属性表集合(attributes_count和attribute)
在Class文件、属性表、方法表中都可以包含自己的属性表集合,用于描述某些场景的专有信息。
与Class文件中其它数据项对长度、顺序、格式的严格要求不同,属性表集合不要求其中包含的属性表具有严格的顺序,并且只要属性的名称不与已有的属性名称重复,任何人实现的编译器可以向属性表中写入自己定义的属性信息。虚拟机在运行时会忽略不能识别的属性,为了能正确解析Class文件,虚拟机规范中预定义虚拟机实现必须能够识别的9项属性。
属性名称 | 使用位置 | 含义
—–|——|—
Code | 方法表 | Java代码编译成的字节码指令
ConstantValue | 字段表 | final关键字定义的常量值
Deprecated | 类文件、字段表、方法表 | 被声明为deprecated的方法和字段
Exceptions | 方法表 | 方法抛出的异常
InnerClasses | 类文件 | 内部类列表
LineNumberTale | Code属性 | Java源码的行号与字节码指令的对应关系
LocalVariableTable | Code属性 | 方法的局部变量描述
SourceFile | 类文件 | 源文件名称
Synthetic | 类文件、方法表、字段表 | 标识方法或字段是由编译器自动生成的
每种属性均有各自的表结构。这9种表结构有一个共同的特点,即均由一个u2类型的属性名称开始,可以通过这个属性名称来判段属性的类型。
Code属性
Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code
属性中。当然不是所有的方法都必须有这个属性(接口中的方法或抽象方法就不存在Code
属性),Code
属性表结构如下。
类型 | 名称 | 数量
—|—-|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
u2 | max_stack | 1
u2 | max_locals | 1
u4 | code_length | 1
u1 | code | code_length
u2 | exception_table_length | 1
exception_info | exception_table | exception_table_length
u2 | attributes_count | 1
attribute_info | attributes | attributes_count
max_stack
操作数栈深度最大值,在方法执行的任何时刻,操作数栈深度都不会超过这个值。虚拟机运行时根据这个值来分配栈帧的操作数栈深度。
max_locals
局部变量表所需存储空间,单位为Slot
。并不是所有局部变量占用的Slot
之和,当一个局部变量的生命周期结束后,其所占用的Slot
将分配给其它依然存活的局部变量使用,按此方式计算出方法运行时局部变量表所需的存储空间。
code_length和code
用来存放Java源程序编译后生成的字节码指令。code_length
代表字节码长度,code
是用于存储字节码指令的一系列字节流。
每一个指令是一个u1
类型的单字节,当虚拟机读到code
中的一个字节码(一个字节能表示256种指令,Java虚拟机规范定义了其中约200个编码对应的指令),就可以判断出该字节码代表的指令,指令后面是否带有参数,参数该如何解释,虽然code_length
占4个字节,但是Java虚拟机规范中限制一个方法不能超过65535条字节码指令,如果超过,Javac
将拒绝编译。
ConstantValue属性
通知虚拟机自动为静态变量赋值,只有被static
关键字修饰的变量(类变量)才可以使用这项属性。
类型 | 名称 | 数量
—|—-|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
u2 | constantvalue_index | 1
可以看出ConstantValue
属性是一个定长属性,其中attribute_length
的值固定为0x00000002
,constantvalue_index
为一常量池字面量类型常量索引(Class文件格式的常量类型中只有与基本类型和字符串类型相对应的字面量常量,所以ConstantValue
属性只支持基本类型和字符串类型)。
对非static
类型变量(实例变量,如:int a = 123;
)的赋值是在实例构造器<init>
方法中进行的。
对类变量(如:static int a = 123;
)的赋值有2种选择,在类构造器<clinit>
方法中或使用ConstantValue
属性。当前Javac编译器的选择是:如果变量同时被static
和final
修饰(虚拟机规范只要求有ConstantValue
属性的字段必须设置ACC_STATIC
标志,对final
关键字的要求是Javac
编译器自己加入的要求),并且该变量的数据类型为基本类型或字符串类型,就生成ConstantValue
属性进行初始化;否则在类构造器<clinit>
方法中进行初始化。
Exceptions属性
列举出方法中可能抛出的受查异常(即方法描述时throws
关键字后列出的异常),与Code
属性平级,与Code
属性包含的异常表不同。
类型 | 名称 | 数量
—|—-|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
u2 | number_of_exceptions | 1
u2 | exception_index_table | number_of_exceptions
number_of_exceptions
抛出number_of_exceptions
种受查异常。
exception_index_table
异常索引集合,一组u2类型exception_index
的集合,每一个exception_index
为一个指向常量池中CONSTANT_Class_info
型常量的索引,代表该受查异常的类型。
InnerClasses属性
该属性用于记录内部类和宿主类之间的关系。如果一个类中定义了内部类,编译器将会为这个类与这个类包含的内部类生成InnerClasses
属性。
类型 | 名称 | 数量
—|—-|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
u2 | number_of_classes | 1
inner_classes_info | inner_classes | number_of_classes
inner_classes
内部类表集合,一组内部类表类型数据的集合,number_of_classes
即为集合中内部类表类型数据的个数。
每一个内部类的信息都由一个inner_classes_info
表来描述,inner_classes_info
表结构如下。
类型 | 名称 | 数量
—|—-|—
u2 | inner_class_info_index | 1
u2 | outer_class_info_index | 1
u2 | inner_name_index | 1
u2 | inner_name_access_flags | 1
inner_class_info_index和outer_class_info_index
指向常量池中CONSTANT_Class_info
类型常量索引,该CONSTANT_Class_info
类型常量指向常量池中CONSTANT_Utf8_info
类型常量,分别为内部类的全限定名和宿主类的全限定名。
inner_name_index
指向常量池中CONSTANT_Utf8_info
类型常量的索引,为内部类名称,如果为匿名内部类,则该值为0。
inner_name_access_flags
和access_flags
一样,是内部类的访问标志。
标志名称 | 标志值 | 含义
—–|—–|—
ACC_PUBLIC | 0x0001 | 内部类是否为public
ACC_PRIVATE | 0x0002 | 内部类是否为private
ACC_PROTECTED | 0x0004 | 内部类是否为protected
ACC_STATIC | 0x0008 | 内部类是否为static
ACC_FINAL | 0x0010 | 内部类是否为final
ACC_INTERFACE | 0x0020 | 内部类是否为一个接口
ACC_ABSTRACT | 0x0400 | 内部类是否为abstract
ACC_SYNTHETIC | 0x1000 | 内部类是否为编译器自动产生
ACC_ANNOTATION | 0x4000 | 内部类是否是一个注解
ACC_ENUM | 0x4000 | 内部类是否是一个枚举
LineNumberTale属性
用于描述Java源码的行号与字节码行号之间的对应关系,非运行时必需属性,会默认生成至Class文件中,可以使用Javac
的-g:none
或-g:lines
关闭或要求生成该项属性信息,其结构如下。
类型 | 名称 | 数量
—|—-|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
u2 | line_number_table_length | 1
line_number_info | line_number_table | line_number_table_length
line_number_table
一组line_number_info
类型数据的集合,其所包含的line_number_info
类型数据的数量为line_number_table_length
。
line_number_info
类型 | 名称 | 数量 | 说明 |
---|---|---|---|
u2 | start_pc | 1 | 字节码行号 |
u2 | line_number | 1 | Java源码行号 |
不生成该属性的最大影响。
- 抛出异常时,堆栈将不会显示出错的行号。
- 调试程序时无法按照源码设置断点。
LocalVariableTable属性
用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,非运行时必需属性,默认不会生成至Class文件中,可以使用Javac
的-g:none
或-g:vars
关闭或要求生成该项属性信息,其结构如下。
类型 | 名称 | 数量
—|—-|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
u2 | local_variable_table_length | 1
local_variable_info | local_variable_table | local_variable_table_lengthlocal_variable_table
一组local_variable_info
类型数据的集合,其所包含的local_variable_info
类型数据的数量为local_variable_table_length
。local_variable_info
类型 | 名称 | 数量 | 说明
—|—-|—-|—
u2 | start_pc | 1 | 局部变量的生命周期开始的字节码偏移量。
u2 | length | 1 | 局部变量作用范围覆盖的长度。
u2 | name_index | 1 | 指向常量池中CONSTANT_Utf8_info类型常量的索引,局部变量名称。
u2 | descriptor_index | 1 | 指向常量池中CONSTANT_Utf8_info类型常量的索引,局部变量描述符。
u2 | index | 1 | 局部变量在栈帧局部变量表中Slot的位置,如果这个变量的数据类型为64位类型(long或double),它占用的Slot为index和index+1这2个位置。start_pc + length
即为该局部变量在字节码中的作用域范围。
不生成该属性的最大影响。 - 当其他人引用这个方法时,所有的参数名称都将丢失,IDE可能会使用诸如arg0、arg1之类的占位符代替原有的参数名称,对代码运行无影响,会给代码的编写带来不便。
- 调试时调试器无法根据参数名称从运行上下文中获取参数值。
SourceFile属性
用于记录生成这个Class文件的源码文件名称,为可选项,可以使用Javac
的-g:none
或-g:source
关闭或要求生成该项属性信息,其结构如下。
类型 | 名称 | 数量
—|—-|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
u2 | sourcefile_index | 1
可以看出SourceFile
属性是一个定长属性,sourcefile_index
是指向常量池中CONSTANT_Utf8_info
类型常量的索引,常量的值为源码文件的文件名。
对大多数文件,类名和文件名是一致的,少数特殊类除外(如:内部类),此时如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错误代码所属的文件名。Deprecated属性和Synthetic属性
这两个属性都属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概念。Deprecated
属性表示某个类、字段或方法已经被程序作者定为不再推荐使用,可在代码中使用@Deprecated
注解进行设置。Synthetic
属性表示该字段或方法不是由Java源码直接产生的,而是由编译器自行添加的(当然也可设置访问标志中的ACC_SYNTHETIC
标志,所有由非用户代码产生的类、方法和字段都应当至少设置Synthetic
属性和ACC_SYNTHETIC
标志位中的一项,唯一的例外是实例构造器<init>
和类构造器<clinit>
方法)Deprecated
属性、Synthetic
属性结构如下。
类型 | 名称 | 数量
—|—-|—
u2 | attribute_name_index | 1
u4 | attribute_length | 1
attribute_length的值必须为0x00000000。