[JVM] Class 类文件结构
------<深入理解Java虚拟机 第3版> 第三部分第6章 读书笔记
文章目录
1. 无关性的基石
Java技术发展之处,就曾考虑并实现了让其他语言运行在Java虚拟机上的可能性。规范文档的发布,也可以把Java的规范拆分成《Java语言规范》和《Java虚拟机规范》两部分。现如今,商业企业和开源机构已经发展处一大批如Kotlin、Coljure、Groovy、JRuby、JPython、Scala等可以运行在Java虚拟机之上的语言。
作为一个通用的、与机器无关的执行平台,任何其他语言的实现都可以将JVM作为代码编译的运行基础,以Class文件作为他们的交付媒介。例如Java编译器可以将Java代码编译成存储字节码的class文件,使用JRuby等其他语言的编译器一样可以将源代码编辑成class文件。JVM不关心class文件的源代码是何种语言。
2. Class类文件的结构
ClassFile {
u4 magic;
u2 minor_version;
u2 major_version;
u2 constant_pool_count;
cp_info constant_pool[constant_pool_count-1];
u2 access_flags;
u2 this_class;
u2 super_class;
u2 interfaces_count;
u2 interfaces[interfaces_count];
u2 fields_count;
field_info fields[fields_count];
u2 methods_count;
method_info methods[methods_count];
u2 attributes_count;
attribute_info attributes[attributes_count];
}
2.1 类文件的两种数据类型
-
无符号数: 基本数据类型,以u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数
-
表:多个无符号数组成的复合数据类型,通常以”_info“结尾。表用于描述有层次关系的复合结构的数据
2.2 Magic Number 和Class文件版本
每个class文件的头四个字节被称为魔法数字(Magic Number),它的唯一作用是确定这个文件是否能被虚拟机接受。Class文件的魔数,0xCAFEBABE(咖啡宝贝?)这个魔数在Java还被称为“Oak”. 见图1.
图1. magic number
紧接着魔法数字后面的4个字节为Class文件的版本号:第5和第6字节Minor Version次版本号,第7和第8字节为Major Version主版本号。如图2, 0x0034, 即十进制的52,说明这个是JDK8的Class文件。
图2. 版本号
表1. JDK版本号对应表
JDK版本 | -source参数 | 版本号 |
---|---|---|
JDK 6 | 1.1~6 | 50.0 |
JDK 7 | 1.1~7 | 51.0 |
JDK 8 | 1.1~8 | 52.0 |
JDK 9 | 6~9 | 53.0 |
JDK 10 | 6~10 | 54.0 |
JDK 11 | 6~11 | 55.0 |
JDK 12 | 6~12 | 56.0 |
JDK 13 | 6~13 | 57.0 |
2.3 常量池
紧接着版本号之后,是常量池入口。常量池是Class文件里的资源仓库,是Class文件结构中与其他项目关联最多的数据。长度不固定,所以入口放置一个u2类型的容量技术。常量池的容量计数是从1开始的。
例如图3, 0x0030即十进制的48,说明该class文件中含有48个常量,#1~#47
图4. 常量池长度
用Javap反编译可以印证1
图5. javap反编译
常量池主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference),字面量比较接近Java语言层面的常量概念,比如文本字符串,被生命为final的常量值等。符号引用包括:
- 被模块导出或者开放的包(package)
- 类和接口的全限定名(Fully Qualified Name) (包名+类名,比如java.lang.String)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
- 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
- 动态调用点和动态常量(Dynamically-computed Call Site、Dynamicaaly-computed Constant)
表2: 常量类型与标志位对应表
类型 | 标志 | 描述 |
---|---|---|
CONSTANT_Uft8_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 | 字段或者方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 表示方法类型 |
CONSTANT_Dynamic_info | 17 | 表示一个动态计算常量 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
CONSTANT_Module_info | 19 | 表示一个模块 |
CONSTAN_Package_info | 20 | 表示一个模块中开放或者导出的包 |
继续之前的栗子,如图5,标识常量池长度的0x0030之后,0x0A, 标志=10,对应上表可发现,常量池中#1 是Methodref.
CONSTANT_Methodref_info {
//The tag item of a CONSTANT_Methodref_info structure has the value CONSTANT_Methodref (10).
u1 tag;
//The class_index item of a CONSTANT_Methodref_info structure must be a class type, not an interface type.
u2 class_index;
//The value of the name_and_type_index item must be a valid index into the constant_pool table. The constant_pool entry at that index must be a CONSTANT_NameAndType_info structure (§4.4.6). This constant_pool entry indicates the name and descriptor of the field or method.
u2 name_and_type_index;
}
结合Methodref的结构来看,可知Class_index是0x0008即#8, NameAndType的index是0x001B, 即#27
图6. 常量池#1详解
图7. javap反编译
在这需要说明一点的是,CONSTANT_Uft8_info的解码。CONSTANT_Uft8_info的结构如下:
//CONSTANT_Uft8_info
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
以常量池#9为例2.
图8. UTF8解码
CONSTANT_Uft8_info中length的最大长度为65535, 故而如果Java程序中定义了超过64KB英文字符的变量或者方法名,是无法编译的。
常量池完整的17种数据类型的结构参见:docs.oracle
2.4 访问标志
在常量池结束之后,紧接着2个字节代表方位标志(access_flags), 这个标志用于识别一些类或者接口的访问信息。
表3. 访问标志
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指定的新语义。JDK1.2之后编译出来的类的这个标志都必须为真 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类此标识为真 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非由用户代码产生的 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
ACC_MODULE | 0x8000 | 标识这是一个模块 |
例子中的类是个public的普通类,ACC_PUBLIC与ACC_SUPER为真, 0x0001|0x0020 =0x0021
图9. 访问标志
2.5 类索引、父类索引和接口索引集合
类索引(this_class)和父类索引(super_class)均问u2类型的数据,接口索引为u2类型的数据的集合。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名(除了java.lang.Object之外,父类索引均不为0)。接口索引为了确定这个类实现了哪些接口。(如果这是个class,则按implements后接口顺序,如果这是个interface,则按extends后的接口顺序)
图10
接口索引的结构,入口为u2类型的接口计数器,表明接口索引表的容量,如没有实现接口则为0
图11
图12
2.6 字段表集合
field_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;//
attribute_info attributes[attributes_count];
}
- access_flag:访问标志,包括字段修饰符(public、private、protected等),实例变量还是类变量(static),可变性(final),并发可见性(volatic),可否被序列化(transient)等
- name_index: 字段名称在常量池中的index
- descriptor_index: 字段描述符在常量池中的index,用来描述字段的数据类型
- attributes_count: 属性表集合的容量
- attribute_info: 见2.8节
表4.access_flags 字段访问标志
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private; usable only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static. |
ACC_FINAL | 0x0010 | Declared final; never directly assigned to after object construction (JLS §17.5). |
ACC_VOLATILE | 0x0040 | Declared volatile; cannot be cached. |
ACC_TRANSIENT | 0x0080 | Declared transient; not written or read by a persistent object manager. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
ACC_ENUM | 0x4000 | Declared as an element of an enum. |
表5. descriptor 字段描述符
FieldType term | Type | Interpretation |
---|---|---|
B | byte | signed byte |
C | char | Unicode character code point in the Basic Multilingual Plane, encoded with UTF-16 |
D | double | double-precision floating-point value |
F | float | single-precision floating-point value |
I | int | integer |
J | long | long integer |
L | ClassName/reference | an instance of class ClassName |
S | short | signed short |
Z | boolean | true or false |
[ | reference | one array dimension(二维数据记为"[[]]") |
V | void | void |
2.7 方法表集合
method_info {
u2 access_flags;
u2 name_index;
u2 descriptor_index;
u2 attributes_count;
attribute_info attributes[attributes_count];
}
- access_flag: 方法的访问标志,见表6
- name_index: 方法名的常量池索引
- descriptor_index: 方法的描述符索引,与字段描述符不同的是,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号中。 例如:int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int gargetCount, int fromIndex) 的描述符为 ([CII[CIII)I
方法中的具体行为,经过javac的编译成字节码后,存放在attribute_info中的code里。详见2.8 属性表集合。
表6. 方法的访问标志
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC | 0x0001 | Declared public ; may be accessed from outside its package. |
ACC_PRIVATE | 0x0002 | Declared private ; accessible only within the defining class. |
ACC_PROTECTED | 0x0004 | Declared protected ; may be accessed within subclasses. |
ACC_STATIC | 0x0008 | Declared static . |
ACC_FINAL | 0x0010 | Declared final ; must not be overridden |
ACC_SYNCHRONIZED | 0x0020 | Declared synchronized ; invocation is wrapped by a monitor use. |
ACC_BRIDGE | 0x0040 | A bridge method, generated by the compiler. |
ACC_VARARGS | 0x0080 | Declared with variable number of arguments. |
ACC_NATIVE | 0x0100 | Declared native ; implemented in a language other than Java. |
ACC_ABSTRACT | 0x0400 | Declared abstract ; no implementation is provided. |
ACC_STRICT | 0x0800 | Declared strictfp ; floating-point mode is FP-strict. |
ACC_SYNTHETIC | 0x1000 | Declared synthetic; not present in the source code. |
2.8 属性表集合
attribute_info {
u2 attribute_name_index;
u4 attribute_length;
u1 info[attribute_length];
}
- attribute_name_index: 属性名在常量池中的index
- attribute_length: 属性表不包括attribute_name_index和attribute_length的长度
在Java SE 12中,预定义的属性已经增加到了29个。见表6. 下文介绍其中关键的常用的属性。
Attribute | class file | Java SE | Section |
---|---|---|---|
ConstantValue | 45.3 | 1.0.2 | §4.7.2 |
Code | 45.3 | 1.0.2 | §4.7.3 |
Exceptions | 45.3 | 1.0.2 | §4.7.5 |
SourceFile | 45.3 | 1.0.2 | §4.7.10 |
LineNumberTable | 45.3 | 1.0.2 | §4.7.12 |
LocalVariableTable | 45.3 | 1.0.2 | §4.7.13 |
InnerClasses | 45.3 | 1.1 | §4.7.6 |
Synthetic | 45.3 | 1.1 | §4.7.8 |
Deprecated | 45.3 | 1.1 | §4.7.15 |
EnclosingMethod | 49.0 | 5.0 | §4.7.7 |
Signature | 49.0 | 5.0 | §4.7.9 |
SourceDebugExtension | 49.0 | 5.0 | §4.7.11 |
LocalVariableTypeTable | 49.0 | 5.0 | §4.7.14 |
RuntimeVisibleAnnotations | 49.0 | 5.0 | §4.7.16 |
RuntimeInvisibleAnnotations | 49.0 | 5.0 | §4.7.17 |
RuntimeVisibleParameterAnnotations | 49.0 | 5.0 | §4.7.18 |
RuntimeInvisibleParameterAnnotations | 49.0 | 5.0 | §4.7.19 |
AnnotationDefault | 49.0 | 5.0 | §4.7.22 |
StackMapTable | 50.0 | 6 | §4.7.4 |
BootstrapMethods | 51.0 | 7 | §4.7.23 |
RuntimeVisibleTypeAnnotations | 52.0 | 8 | §4.7.20 |
RuntimeInvisibleTypeAnnotations | 52.0 | 8 | §4.7.21 |
MethodParameters | 52.0 | 8 | §4.7.24 |
Module | 53.0 | 9 | §4.7.25 |
ModulePackages | 53.0 | 9 | §4.7.26 |
ModuleMainClass | 53.0 | 9 | §4.7.27 |
NestHost | 55.0 | 11 | §4.7.28 |
NestMembers | 55.0 | 11 | §4.7.29 |
Record | 60.0 | 16 | §4.7.30 |
红色标注:5个对JVM能正确解读class文件至关重要的属性
蓝色标注:12个对Java SE类库能正确解读class文件至关重要的属性
2.8.1 Code属性
Code属性是Class文件中最重要的一个属性,用户描述代码指令,所有的其他数据项目都用于描述元数据。
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];
}
[UTF8 codepage]UTF-8 codepage ↩︎