【摘要】 Java虚拟机和Class文件是Java实现系统无关性的基石。
Class文件是JVM实现语言无关性的基石。Class文件中包含了Java虚拟机指令集、符号表以及若干其他辅助信息。
每一个 Class 文件对应于一个如下所示的 ClassFile 结构体:
ClassFile {
u4 magic;
u2 minor_version;
u2 majo…
Java虚拟机和Class文件是Java实现系统无关性的基石。
Class文件是JVM实现语言无关性的基石。
Class文件中包含了Java虚拟机指令集、符号表以及若干其他辅助信息。
每一个 Class 文件对应于一个如下所示的 ClassFile 结构体:
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];
}
简单看一下各项的含义:
由于 Class 文件结构没有任何分隔符,所以无论是每个数据项的的顺序还是数量,都是严格限定的,哪个字节代表什么含义,长度多少,先后顺序如何,都是不允许改变的。
接下来我们来具体学习每项的含义。
1、魔数
这是基本上每个Java开发人员的第一个Java程序:
public class HelloWorld { public static void main(String[] args) { System.out.println("Hello World"); }
}
我使用的是Idea工具,运行,target目录下会生成对应的class文件,为了查看文件的十六进制信息,我们可以安装一个插件HexView
。
安装完成之后,选择class文件,右键 HexView,打开后的十六进制如下:
第一行中有一串特殊的字符 cafebabe
,它就是一个魔数,是 JVM 识别 class 文件的标志,JVM 会在验证阶段检查 class 文件是否以该魔数开头,如果不是则会抛出 ClassFormatError
。
这段字节很有意思——咖啡宝贝,Java原来不止是咖啡,还是宝贝?
2、版本号
紧跟着魔数后面的四个字节 0000 0031
存储的是 class 文件的版本号:第 5 和第 6 个字节是次版本号(Minor Version),第 7 和第 8 个字节是主版本号(Major Version)。
Java的版本号是从 45 开始的,JDK1.1 之后的每个 JDK 大版本发布主版本号向上加1(JDK1.0JDK1.1使用了45.045.3的版本号),高版本的 JDK 能向下兼容以前版本的 Class 文件,但不能运行以后版本的 Class 文件,即使文件格式未发生变化。
0000 0031 对应的十进制为49,是JDK8的内部版本号。
3、常量池
紧接着主、次版本号之后的是常量池入口。
由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。与Java中语言习惯不同,这个容量计数是从1而不是0开始的。
如图所示,常量池容量为十六进制数0x0022,即十进制的34,这就代表常量池中有33项常量,索引值范围为1~33。Class文件结构中只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始。
常量池中主要存放两大类常量:字面量(Literal)
和符号引用(Symbolic References)
。字面量比较接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,主要包括下面几类常量:
-
被模块导出或者开放的包(Package)
-
类和接口的全限定名(Fully Qualified Name)
-
字段的名称和描述符(Descriptor)
-
方法的名称和描述符
-
方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
-
动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)
这17类常量结构只有一个相同之处,表结构起始的第一位是个u1类型的标志位(tag),代表着当前常量属于哪种常量类型。
17种常量类型所代表的具体含义如表所示:
类型 | 标志 | 描述 |
---|---|---|
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 | 字段或方法的部分符号引用 |
CONSTANT_MethodHandle_info | 15 | 表示方法句柄 |
CONSTANT_MethodType_info | 16 | 表示方法类型 |
CONSTANT_Dynamic_info | 17 | 表示一个动态计算常量 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
CONSTANT_Moudle_info | 19 | 表示一个模块 |
CONSTANT_Package_info | 20 | 表示一个模块中开放或者导出的包 |