按照《java虚拟机规范SE7》章节顺序整理的笔记。
目录:
- ClassFile格式(注:也就是class文件的总结构)
- 描述符和签名
- 常量池
- 字段
- 方法
- 属性
- Java虚拟机代码约束
- Class文件校验
第四章:class文件格式
这一章详细的介绍了class文件的格式,包括class文件的格式,class文件具体的内容。这些内容均可以通过自己编写一个简单的类,并使用javap反编译来对照着阅读。
<1>. ClassFile格式
这表示class文件的总结构,也就是说:满足虚拟机规范的class文件的构成。
值得注意的是:
- 其中u1,u2,u4分别代表这个项打大小为 1,2,4个字节。
- 其中的cp_info, field_info, method_info, attribute_info表示这个表中项的结构,同时也说明这个项的大小暂时是不确定的。
- 结构中一共有五个表:常量池表,接口表,字段表,方法表,属性表
而其中只有常量池表是从1开始索引,而其他表均从0开始索引,所以常量池计数器比实际常量的大小多1。 - 需要重点提出的是,每个class仅仅是针对当前类,或者他的直接父类,如:
1.字段表仅仅包含当前类或接口声名的字段,并不包含父类或父接口。
2.方法表仅仅包含当前类或接口声明的方法,并不包含父类或父接口。
ClassFile {
u4 magic; //魔数,唯一的作用是标志当前class是否符合虚拟机规范,
//唯一的值为:0xCOFEBABE(咖啡宝宝?)
u2 minor_version; //副版本号
u2 major_version; //主版本号
//主版本号和副版本号结合起来,作用是表示当前的class是否被某个版本的java虚拟机支持。
//各个版本的虚拟机都有自己支持的主副版本号范围。
u2 constant_pool_count; //常量池计数器,值为后面常量池表成员数加1.
cp_info constant_pool[constant_pool_count-1]; //常量池表,
//每一项都是cp_info结构,索引从1到constant_pool_count-1。
u2 access_flags; //访问标志,标志类或接口的访问权限及基础属性(值见下表)。
u2 this_class; //类索引,表示当前文件的类或接口,值得注意,类并非一定与文件名相同,如:内部类。
u2 super_class; //直接父类
//如果值为0:表示没有父类,而java中没有父类的只有java.lang.Object
//如果不是0:表示这个一个类或接口,他的值必须是常量池中的CONSTANT_Class_info的值
u2 interfaces_count; //接口计数器,大小为当前类直接父接口数量。
u2 interfaces[interfaces_count]; //接口表
//每一项的值都是常量池中的CONSTANT_Class_info类型的值。
//索引从0开始。
u2 fields_count; //字段计数器 ,大小为当前类或接口所定义的字段。
field_info fields[fields_count]; //字段表
//每一项都是field_info结构。索引从0开始。
u2 methods_count; //方法计数器,大小为当前类或接口所定义的方法。
method_info methods[methods_count]; //方法表
//每一项都是method_info结构。索引从0开始。
u2 attributes_count; //属性计数器,大小为class文件中的属性数量。
attribute_info attributes[attributes_count]; //属性表
每一项都是attribute_info结构。索引从0开始。
}
访问标志一览表 (access_flags):
标记名 | 值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 可以被包的类外访问 |
ACC_FINAL | 0x0010 | 不允许有子类 |
ACC_SUPER | 0x0020 | 当用到invokespecial指令时,需要特殊处理的父类方法 |
ACC_INTERFACE | 0x0200 | 标识定义的是接口而不是类 |
ACC_ABSTRACT | 0x0400 | 不能被实例化 |
ACC_SYNTHETIC | 0x1000 | 标识并非Java源码生成的代码 |
ACC_ANNOTATION | 0x2000 | 标识注解类型 |
ACC_ENUM | 0x4000 | 标识枚举类型 |
<2>. 描述符和签名
描述符:一个描述字段或方法的类型的字符串。
可分为:
- 字段描述符
- 方法描述符
a.字段描述符:描述了字段的类型。
- 描述原始类型
字符 | 类型 | 含义 |
---|---|---|
B | byte | 有符号字节型数 |
C | char | Unicode字符,UTF-16编码 |
D | double | 双精度浮点数 |
F | float | 单精度浮点数 |
I | int | 整型数 |
J | long | 长整数 |
S | short | 有符号短整数 |
Z | boolean | 布尔值 true/false |
L Classname; | reference | 一个名为的实例 |
[ | reference | 一个一维数组 |
-
描述对象
格式:L+Classname
注:这里的Classname是类的全限定名,如Object类,Ljava.lang.Object -
描述数组
格式:[ + ComponentType
例如:[ I , [[java.lang.Object
b. 方法描述符:描述了方法的参数类型,参数大小,和返回类型。
格式:( 使用字段描述符描述的0或n个参数 ) 返回类型同样使用字段描述符表示
例如:方法 int method(int a,Object b) 描述为 ( I [java.lang.Object; ) I ;
注意:对于实例方法中的this参数,并不显示的写在方法描述符中,它是在java虚拟机调用方法的时候隐式传递。
签名:是用于描述字段、方法和类型定义中的泛型信息的字符串。
签名是用于给Java语言使用的描述信息编码,不在Java虚拟机系统使用的类型中。
- 类签名,作用是把Class申明的类型信息编译成对应的签名信息。
- 字段类型签名,作用是将字段、参数或局部变量的类型编译成对应的签名信息。
- 方法签名,作用是将方法中所有的形式参数的类型编译成相应的签名信息(或将它们参数化)。
<3>. 常量池
常量池中每一项都是一个cp_info结构,而cp_info的结构如下:
cp_info {
u1 tag;
u1 info[]; //这个值的大小由tag标志决定。
}
例如CONSTANT_Class_info 表示为:
CONSTANT_Class_info{
u1 tag;
u2 name_index;
}
tag标签一共有14种,如下表:
常量类型 | 值 |
---|---|
CONSTANT_Class_info | 7 |
CONSTANT_Fieldref_info | 9 |
CONSTANT_Methodref_info | 10 |
CONSTANT_InterfaceMethodref_info | 11 |
CONSTANT_String_info | 8 |
CONSTANT_Integer_info | 3 |
CONSTANT_Float_info | 4 |
CONSTANT_Long_info | 5 |
CONSTANT_Double_info | 6 |
CONSTANT_NameAndType_info | 12 |
CONSTANT_Utf8_info | 1 |
CONSTANT_MethodHandle_info | 15 |
CONSTANT_MethodType_info | 16 |
CONSTANT_InvokeDynamic_info | 18 |
类 ——CONSTANT_Class_info 格式:
CONSTANT_Class_info{
u1 tag; //值为 7
u2 name_index; //name_index项的值,必须是对常量池的一个有效索引。
//常量池在该索引处的项必须是CONSTANT_Utf8_info(§4.4.7)结构,
//代表一个有效的类或接口二进制名称的内部形式。
}
字段,方法,接口方法——CONSTANT_Fieldref_info, CONSTANT_Methodref_info和CONSTANT_InterfaceMethodref_info的格式相同:
CONSTANT_Fieldref_info {
u1 tag; //值为 9
u2 class_index; //class_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,既可以是类也可以是接口。
u2 name_and_type_index; //name_and_type_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,必须是字段描述符。
}
CONSTANT_Methodref_info {
u1 tag; //值为 10
u2 class_index; //class_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,且必须是类(不能是接口)。
u2 name_and_type_index; //name_and_type_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,必须是方法描述符
}
CONSTANT_InterfaceMethodref_info {
u1 tag; //值为 11
u2 class_index; //class_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_Class_info结构,且必须是接口(不能是类)。
u2 name_and_type_index; //name_and_type_index项的值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,必须是方法描述符
}
*字符串——CONSTANT_String_info 格式:
CONSTANT_String_info {
u1 tag; //值为 8
u2 string_index; //必须是对常量池的有效索引,索引处的项必须是CONSTANT_Utf8_info
}
数值常量——CONSTANT_Integer_info,CONSTANT_Float_info 格式:
CONSTANT_Integer_info {
u1 tag; //值为 3
u4 bytes; //int类型常量值
}
CONSTANT_Float_info {
u1 tag; //值为 4
u4 bytes; //float类型常量值
}
float类型的值按照下面方法表示,bytes项的值首先被转换成一个int常量的bits:
- 如果bits值为0x7f800000,表示float值为正无穷。
- 如果bits值为0xff800000,表示float值为负无穷。
- 如果bits值在范围0x7f800001到0x7fffffff或者0xff800001到0xffffffff内,表示float值为NaN。
- 在其它情况下,设s、e、m,它们值根据bits和如下公式计算:
int s =((bits >> 31) == 0) ? 1 : -1;
int e =((bits >> 23) & 0xff);
int m =(e == 0) ? bits & 0x7fffff) << 1 : (bits & 0x7fffff) | 0x800000;
则float的浮点值为数值表达式s·m·2^(e–150)的计算结果。
数值常量——CONSTANT_Long_info和CONSTANT_Double_info 格式:
在Class文件的常量池中,所有的8字节的常量都占两个表成员(项)的空间。如果一个CONSTANT_Long_info或CONSTANT_Double_info结构的项在常量池中的索引为n,则常量池中下一个有效的项的索引为n+2,此时常量池中索引为n+1的项有效但必须被认为不可用。
CONSTANT_Long_info {
u1 tag; //值为 5
u4 high_bytes;
u4 low_bytes;
}
CONSTANT_Double_info {
u1 tag; //值为6
u4 high_bytes;
u4 low_bytes;
}
Long:high_bytes和low_bytes项用于共同表示long型常量,构造形式为
((long) high_bytes << 32) + low_bytes
也就是将高位在前,地位在后
Double:与上一个Float类似,首先被转换成一个long常量的bits:
- 如果bits值为0x7ff0000000000000L,表示double值为正无穷。
- 如果bits值为0xfff0000000000000L,表示double值为负无穷。
- 如果bits值在范围0x7ff0000000000001L到 0x7fffffffffffffffL或者0xfff0000000000001L到 0xffffffffffffffffL内,表示double值为NaN。
- 在其它情况下,设s、e、m,它们的值根据bits和如下公式计算:
int s =((bits >> 63) == 0) ? 1 : -1;
int e =(int)((bits >> 52) & 0x7ffL);
long m =(e == 0) ? (bits & 0xfffffffffffffL) << 1 : (bits & 0xfffffffffffffL) | 0x10000000000000L;
则double的浮点值为数学表达式s·m·2e – 1075的计算结果。
表示字段或方法——CONSTANT_NameAndType_info 格式:
CONSTANT_NameAndType_info {
u1 tag; //值为 12
u2 name_index; //必须是对常量池的有效索引CONSTANT_Utf8_info,这个结构要么表示特殊的方法名<init>,要么表示一个有效的字段或方法的非限定名。
u2 descriptor_index; //索引处的项必须是CONSTANT_Utf8_info,这个结构表示一个有效的字段描述符或方法描述符。
}
表示字符串常量——CONSTANT_Utf8_info 格式 :
CONSTANT_Utf8_info {
u1 tag; //值为 1
u2 length; //指明了bytes[]数组的长度
u1 bytes[length]; //bytes[]是表示字符串值的byte数组,bytes[]数组中每个成员的byte值都不会是0,也不在0xf0至0xff范围内。
}
方法句柄——CONSTANT_MethodHandle_info 格式:
CONSTANT_MethodHandle_info {
u1 tag; //值为 15
u1 reference_kind; //值必须在1-9之间,根据这个值的不同,将造成下面项的不同。
u2 reference_index;
}
reference的取值:
- 如果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),那么方法句柄对应的方法不能为实例初始化()方法或类初始化方法()。
- 如果reference_kind项的值是8(REF_newInvokeSpecial),那么方法句柄对应的方法必须为实例初始化()方法。
表示invokedynamic指令所使用到的引导方法——CONSTANT_InvokeDynamic_info 格式:
CONSTANT_InvokeDynamic_info {
u1 tag; //值为 18
u2 bootstrap_method_attr_index;
u2 name_and_type_index;
}
<4>. 字段
字段每一项都是一个field_info结构:
field_info {
u2 access_flags; //字段的访问标志,包括访问限制,基础属性
u2 name_index; //必须是对常量池的一个有效索引。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的非全限定名。
u2 descriptor_index; //索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的字段的描述符
u2 attributes_count; //字段的属性计数器
attribute_info attributes[attributes_count]; //attributes表的每一个成员的值必须是attribute结构,一个字段可以有任意个关联属性。
}
字段访问标志一览表:
标记名 | 值 | 说明 |
---|---|---|
ACC_PUBLIC | 0x0001 | public,表示字段可以从任何包访问。 |
ACC_PRIVATE | 0x0002 | private,表示字段仅能该类自身调用。 |
ACC_PROTECTED | 0x0004 | protected,表示字段可以被子类调用。 |
ACC_STATIC | 0x0008 | static,表示静态字段。 |
ACC_FINAL | 0x0010 | final,表示字段定义后值无法修改(JLS §17.5)。 |
ACC_VOLATILE | 0x0040 | volatile,表示字段是易变的。 |
ACC_TRANSIENT | 0x0080 | transient,表示字段不会被序列化。 |
ACC_SYNTHETIC | 0x1000 | 表示字段由编译器自动产生。 |
ACC_ENUM | 0x4000 | enum,表示字段为枚举类型。 |
<5>. 方法
方法的每一项都是一个method_info结构:
method_info {
u2 access_flags; //方法访问标志
u2 name_index; //常量池在该索引处的项必须是CONSTANT_Utf8_info结构,它要么表示初始化方法的名字(<init>或<clinit>),要么表示一个方法的有效的非全限定名。
u2 descriptor_index; //。常量池在该索引处的项必须是CONSTANT_Utf8_info结构,表示一个有效的方法的描述符 注意:本规范在未来的某个版本中可能会要求当access_flags项的ACC_VARARGS标志被设置时,方法描述符中的最后一个参数必须是数组类型。
u2 attributes_count; //方法属性计数器
attribute_info attributes[attributes_count]; //attributes表的每一个成员的值必须是attribute结构,一个方法可以有任意个与之相关的属性。
}
方法访问标志一览表:
标记名 | 值 | 说明 |
---|---|---|
ACC_PUBLIC | 0x0001 | public,方法可以从包外访问 |
ACC_PRIVATE | 0x0002 | private,方法只能本类中访问 |
ACC_PROTECTED | 0x0004 | protected,方法在自身和子类可以访问 |
ACC_STATIC | 0x0008 | static,静态方法 |
ACC_FINAL | 0x0010 | final,方法不能被重写(覆盖) |
ACC_SYNCHRONIZED | 0x0020 | synchronized,方法由管程同步 |
ACC_BRIDGE | 0x0040 | bridge,方法由编译器产生 |
ACC_VARARGS | 0x0080 | 表示方法带有变长参数 |
ACC_NATIVE | 0x0100 | native,方法引用非java语言的本地方法 |
ACC_ABSTRACT | 0x0400 | abstract,方法没有具体实现 |
ACC_STRICT | 0x0800 | strictfp,方法使用FP-strict浮点格式 |
ACC_SYNTHETIC | 0x1000 | 方法在源文件中不出现,由编译器产生 |
方法属性表的值可以有:
- Code
- Exceptions
- Synthetic
- Signature
- Deprecated
- untimeVisibleAnnotations
- RuntimeInvisibleAnnotations
- RuntimeVisibleParameterAnnotations
- RuntimeInvisibleParameterAnnotations
- AnnotationDefault
<6>. 属性
属性的每一项都是attribute_info结构:
attribute_info {
u2 attribute_name_index; //是常量池中的CONSTANT_Utf8_info索引。
u4 attribute_length;
u1 info[attribute_length];
}
ConstantValue属性:
位于field_info结构的属性表中,表示一个常量字段的值,在一个field_info结构的属性表中最多只能有一个ConstantValue属性。
格式:
ConstantValue_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 constantvalue_index;
}
Code属性:
位于method_info结构的属性表,一个Code属性只为唯一一个方法、实例类初始化方法或类初始化方法保存Java虚拟机指令及相关辅助信息。
如果方法被声明为native或者abstract类型,那么对应的method_info结构不能有明确的Code属性。
格式:
Code_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 max_stack;
u2 max_locals;
u4 code_length;
u1 code[code_length];
u2 exception_table_length;
{
u2 start_pc;
u2 end_pc;
u2 handler_pc;
u2 catch_type;
}
exception_table[exception_table_length];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
StackMapTable属性:
位于Code属性的属性表中,这个属性会在虚拟机类加载的类型阶段被使用。
格式:
StackMapTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_entries;
stack_map_frame entries[number_of_entries];
}
Exceptions属性:
它位于method_info结构的属性表中,指出了一个方法需要检查的可能抛出的异常。
格式:
Exceptions_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_exceptions;
u2 exception_index_table[number_of_exceptions];
}
InnerClasses属性:
位于ClassFile结构的属性表,支持内部类和内部接口而引入。
格式:
InnerClasses_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 number_of_classes;
{
u2 inner_class_info_index;
u2 outer_class_info_index;
u2 inner_name_index;
u2 inner_class_access_flags;
}
classes[number_of_classes];
}
EnclosingMethod属性:
位于ClassFile结构的属性表,且仅当Class为局部类或者匿名类时,才能具有EnclosingMethod属性,一个类中只能有一个。
格式:
EnclosingMethod_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 class_index
u2 method_index;
}
Synthetic属性:
位于ClassFile中的属性表,如果一个类成员没有在源文件中出现,则必须标记带有Synthetic属性,或者设置ACC_SYNTHETIC标志。
格式:
Synthetic_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
Signature属性:
位于ClassFile,field_info或method_info结构的属性表中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量或参数化类型,则Signature属性会为它记录泛型签名信息。
格式:
Signature_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 signature_index;
}
SourceFile属性:
位于ClassFile结构的属性表,一个ClassFile结构中的属性表最多只能包含一个SourceFile属性。
格式:
SourceFile_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 sourcefile_index;
}
SourceDebugExtension 属性:
位于ClassFile(§4.1)结构属性表,最多只能包含一个SourceDebugExtension属性。
格式:
SourceDebugExtension_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 debug_extension[attribute_length];
}
LineNumberTable属性:
位于Code结构的属性表,用于确定源文件中行号表示的内容在Java虚拟机的code[]数组中对应的部分。
格式:
LineNumberTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 line_number_table_length;
{
u2 start_pc;
u2 line_number;
}
line_number_table[line_number_table_length];
}
LocalVariableTable属性:
位于Code属性的属性表中,用于确定方法在执行过程中局部变量的信息。
格式:
LocalVariableTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_table_length;
{
u2 start_pc;
u2 length;
u2 name_index;
u2 descriptor_index;
u2 index;
}
local_variable_table[local_variable_table_length];
}
LocalVariableTypeTable属性:
位于Code的属性表,用于给调试器确定方法在执行中局部变量的信息。
格式:
LocalVariableTypeTable_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 local_variable_type_table_length;
{
u2 start_pc;
u2 length;
u2 name_index;
u2 signature_index;
u2 index;
}
local_variable_type_table[local_variable_type_table_length];
}
Deprecated属性:
位于ClassFile, field_info 或 method_info结构的属性表中,标记了此属性,则说明它将会在后续某个版本中被取代。
格式:
Deprecated_attribute {
u2 attribute_name_index;
u4 attribute_length;
}
RuntimeVisibleAnnotations属性:
位于ClassFile, field_info或method_info结构的属性表中,用于保存Java语言中的类、字段或方法的运行时的可见注解。
格式:
RuntimeVisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
RuntimeInvisibleAnnotations 属性:
位于ClassFile, field_info或method_info结构的属性表中,用于保存Java语言中的类、字段或方法的运行时的非可见注解。
格式:
RuntimeInvisibleAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_annotations;
annotation annotations[num_annotations];
}
RuntimeVisibleParameterAnnotations属性:
位于method_info结构的属性表中 ,用于保存对应方法的参数的所有运行时可见Java语言注解
格式:
RuntimeVisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{
u2 num_annotations;
annotation annotations[num_annotations];
}
parameter_annotations[num_parameters];
}
RuntimeInvisibleParameterAnnotations属性:
位于method_info结构的属性表中,用于保存对应方法的参数的所有运行时非可见的Java语言注解。
格式:
RuntimeInvisibleParameterAnnotations_attribute {
u2 attribute_name_index;
u4 attribute_length;
u1 num_parameters;
{
u2 num_annotations;
annotation annotations[num_annotations];
}
parameter_annotations[num_parameters];
}
AnnotationDefault属性:
位于某些特殊的method_info结构的属性表中,这些结构表示注解类型的元素,用于保存method_info结构表示的元素的默认值。
格式:
AnnotationDefault_attribute {
u2 attribute_name_index;
u4 attribute_length;
element_value default_value;
}
BootstrapMethods属性:
位于ClassFile结构的属性表中,用于保存invokedynamic指令引用的引导方法限定符。
格式:
BootstrapMethods_attribute {
u2 attribute_name_index;
u4 attribute_length;
u2 num_bootstrap_methods;
{
u2 bootstrap_method_ref;
u2 num_bootstrap_arguments;
u2 bootstrap_arguments[num_bootstrap_arguments];
}
bootstrap_methods[num_bootstrap_methods];
}
<7>. Java虚拟机代码约束
这里使用了很多对java虚拟机的约束,由于内容过多,且对只是为了解一些java虚拟机情况的我来说并不需要深入理解,便不摘录了。
至此,对于javac编译的class文件也有了深入的理解,对java虚拟机内部结构也有了认识。后面的内容主要就是:Java虚拟机如何加载class文件。