• 魔数
  • 版本
  • 常量池
  • 访问标志

示例源码:

1
2
3
4
5
6
7
package test;

public class Test {
public static void main(String[] args) {
System.out.println("Hello World");
}
}

示例Class文件:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
CA FE BA BE(Class魔数) 
--------------版本号-------------------
00 00(次版本号)
00 34(主版本号)
--------------常量池-------------------
00 22(十进制34,33个常量)
07(CONSTANT_Class_info)
00 02(常量池中第二项)
01(CONSTANT_Utf8_info)
00 09(字符串长度9字节)
74(t) 65(e) 73(s) 74(t) 2F(/) 54(T) 65(e) 73(s) 74(t)
07(CONSTANT_Class_info)
00 04(常量池中第4项)
01(CONSTANT_Utf8_info)
00 10(字符串长度是16字节)
6A(j) 61(a) 76(v) 61(a) 2F(/) 6C(l) 61(a) 6E(n) 67(g) 2F(/) 4F(O) 62(b) 6A(j) 65(e) 63(c) 74(t)
01(CONSTANT_Utf8_info)
00 06(字符串长度6字节)
3C(<) 69(i) 6E(n) 69(i) 74(t) 3E(>)
01(CONSTANT_Utf8_info)
00 03(字符串长度3字节)
28(() 29()) 56(V)
01(CONSTANT_Utf8_info)
00 04(字符串长度4字节)
43(C) 6F(o) 64(d) 65(e)
0A(CONSTANT_Methodref_info)
00 03(3,指向声明方法的类描述符CONSTANT_Class_info的索引项)
00 09(9,指向名称及类型描述符CONSTANT_NameAndType_info的索引项)
0C(CONSTANT_NameAndType_info字段或符号的部分引用)
00 05(5,指向该字段或方法名称常量项的索引)
00 06(6,指向该字段或方法描述符常量项的索引)
01(CONSTANT_Utf8_info)
00 0F(字符串长度15字节)
4C(L) 69(i) 6E(n) 65(e) 4E(N) 75(u) 6D(m) 62(b) 65(e) 72(r) 54(T) 61(a) 62(b) 6C(l) 65(e)
01(CONSTANT_Utf8_info)
00 12(字符串长度18字节)
4C(L) 6F(o) 63(c) 61(a) 6C(l) 56(V) 61(a) 72(r) 69(i) 61(a) 62(b) 6C(l) 65(e) 54(T) 61(a) 62(b) 6C(l) 65(e)
01(CONSTANT_Utf8_info)
00 04(字符串长度4字节)
74(t) 68(h) 69(i) 73(s)
01(CONSTANT_Utf8_info)
00 0B(字符串长度11字节)
4C(L) 74(t) 65(e) 73(s) 74(t) 2F(/) 54(T) 65(e) 73(s) 74(T) 3B(;)
01(CONSTANT_Utf8_info)
00 04(字符串长度4字节)
6D(m) 61(a) 69(i) 6E(n)
01(CONSTANT_Utf8_info)
00 16(字符串长度22字节)
28(() 5B([) 4C(L) 6A(j) 61(a) 76(v) 61(a) 2F(/) 6C(l) 61(a) 6E(n) 67(g) 2F(/) 53(S) 74(t) 72(r) 69(i) 6E(n) 67(g) 3B(;) 29()) 56(V)
09(CONSTANT_Fieldref_info)
00 11(17,指向声明字段的类或者接口描述符CONSTANT_Class_info的索引项)
00 13(19,指向字段描述符CONSTANT_NameAndType的索引项)
07(CONSTANT_Class_info)
00 12(18,指向全限定名常量项的索引)
01(CONSTANT_Utf8_info)
00 10(字符串长度16字节)
6A(j) 61(a) 76(v) 61(a) 2F(/) 6C(l) 61(a) 6E(n) 67(g) 2F(/) 53(s) 79(y) 73(s) 74(t) 65(e) 6D(m)
0C(CONSTANT_NameAndType_info)
00 14(20,指向该字段或方法名称常量项的索引)
00 15(21,指向该字段或方法描述符常量项的索引)
01(CONSTANT_Utf8_info)
00 03(字符串长度3字节)
6F(o) 75(u) 74(t)
01(CONSTANT_Utf8_info)
00 15(字符串长度21字节)
4C(L) 6A(j) 61(a) 76(v) 61(a) 2F(/) 69(i) 6F(o) 2F(/) 50(P) 72(r) 69(i) 6E(n) 74(t) 53(S) 74(t) 72(r) 65(e) 61(a) 6D(m) 3B(;)
08(CONSTANT_String_info)
00 17(23,指向字符串字面量的索引)
01(CONSTANT_Utf8_info)
00 0B(字符串长度11字节)
48(H) 65(e) 6C(l) 6C(l) 6F(o) 20( ) 57(W) 6F(o) 72(r) 6C(l) 64(d)
0A(CONSTANT_Methodref_info)
00 19(25,指向声明方法的类描述符CONSTANT_Class_info的索引项)
00 1B(27,指向名称及类型描述符CONSTANT_NameAndType的索引项)
07(CONSTANT_Class_info)
00 1A(26,指向全限定名常量项的索引)
01(CONSTANT_Utf8_info)
00 13(字符串长度19字节)
6A(j) 61(a) 76(v) 61(a) 2F(/) 69(i) 6F(o) 2F(/) 50(P) 72(r) 69(i) 6E(n) 74(t) 53(S) 74(t) 72(r) 65(e) 61(a) 6D(m)
0C(CONSTANT_NameAndType_info)
00 1C(28,指向该字段或方法名称常量项的索引)
00 1D(29,指向该字段或方法描述符常量项的索引)
01(CONSTANT_Utf8_info)
00 07(字符串长度7字节)
70(p) 72(r) 69(i) 6E(n) 74(t) 6C(l) 6E(n)
01(CONSTANT_Utf8_info)
00 15(字符串长度21字节)
28(() 4C(L) 6A(j) 61(a) 76(v) 61(a) 2F(/) 6C(l) 61(a) 6E(n) 67(g) 2F(/) 53(S) 74(t) 72(r) 69(i) 6E(n) 67(g) 3B(;) 29()) 56(V)
01(CONSTANT_Utf8_info)
00 04(字符串长度4字节)
61(a) 72(r) 67(g) 73(s)
01(CONSTANT_Utf8_info)
00 13(字符串长度19字节)
5B([) 4C(L) 6A(j) 61(a) 76(v) 61(a) 2F(/) 6C(l) 61(a) 6E(n) 67(g) 2F(/) 53(S) 74(t) 72(r) 69(i) 6E(n) 67(g) 3B(;)
01(CONSTANT_Utf8_info)
00 0A(字符串长度10字节)
53(S) 6F(o) 75(u) 72(r) 63(c) 65(e) 46(F) 69(i) 6C(l) 65(e)
01(CONSTANT_Utf8_info)
00 09(字符串长度9字节)
54(T) 65(e) 73(s) 74(t) 2E(.) 6A(j) 61(a) 76(v) 61(a)
--------------访问标志-------------------
00 21(ACC_PUBLIC、ACC_SUPER,0x0001|0x0020=0x0021)

魔数

1
CA FE BA BE

作用

确定这个文件是否位一个能被虚拟机接受的Class文件

位置

每个Class文件的头4个字节

原因

安全。使用魔数而不是后缀名的理由是后缀名可以随意改动,而文件格式的制定者可以自由的选择魔数值,只要这个魔数值还没有被广泛采用过同时又不会引起混淆即可。

历史

在Java语言还称作“Oak”时(1991年前后)确定,据Java开发小组最初的关键成员Patrick Naughton说:“我们一直寻找一些好玩的、容易记忆的东西,它象征着著名咖啡品牌Peet’s Coffee深受欢迎的Baristas咖啡。“

Class文件版本

1
00 00(次版本号) 00 34(主版本号)

作用

标志JDK版本

位置

  • Minor Version次版本号:每个Class文件的第5、6字节
  • Major Version主版本号:每个Class文件的第7、8字节

规律

  • 起始版本号:45
  • JDK1.1后每个大版本发布主版本号向上加1
    • JDK1.0~1.1使用了45.0~45.3

兼容

高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件,即使文件格式并未发生任何变化,虚拟机也必须拒绝执行超过其版本号的Class文件

工具

All:Sublime Text

Windows:WinHex

Mac:Hex Fiend

版本号

编译器版本 十进制
1.1 45
1.2 46
1.3 47
1.4 48
1.5 49
1.6 50
1.7 51
1.8 52
1.9 53

如果带-target 1.x,Class文件的版本号是目标编译器版本号。

如:JDK1.7.0

  • 默认为-target 1.7,则版本号为51.0
  • -target 1.6,则版本号为50.0
  • -target 1.4,则版本号为48

上图中主版本号为:0x0034,即十进制52,也就是1.8

常量池

1
00 22

作用

Class文件之中的资源仓库

位置

偏移地址:0x00000008

特点

  • Class文件结构中与其他项目关联最多的数据类型

  • 占用Class文件空间最大的数据项目之一

  • 在Class文件中第一个出现的表类型数据项目

规律

常量池中常量是不固定的,所以在需要在入口放置一个类型为u2的数据,代表常量池容量计数值(constant_pool_count),计数从1开始。

  • 满足后面某些指向常量池的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况就可以把索引值置为0来表示。
  • Class文件结构中只有常量池的容量计数是从1开始的,对于其他集合类型的容量计数,都是从0开始的(包括索引集合、字段表集合、方法表集合等)

例如上面的0x0022,即10进制的34,代表常量池中有33项常量,索引值范围为1~33

内容

  • 常量池中主要存放两大类常量
    • 字面量(Literal):比较接近Java语言层面的常量概念
      • 文本字符串、生命为final的常量值等
    • 符号引用(Symbolic Referrences):编译原理方面的概念
      • 类和接口的全限定名(Fully Qualified Name)
      • 字段的名称和描述符(Descriptor)
      • 方法的名称和描述符

Java代码在进行Javac编译的时候,在虚拟机加载Class文件的时候进行动态连接,所以,在Class文件中不会保存各个方法、字段的最终内存布局信息,当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中。

下面表格为常量池的项目类型

类型 标志 描述
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_InvokeDynamic_info 18 表示一个动态方法调用点

具体每个常量项的结构,请见Java虚拟机规范。举例:JVM Specification-1.7-4.41

实例

1
07 00 02 01 00 09 74 65 73 74 2F 54 65 73 74

0x07CONSTANT_Class_info,类或接口的引用

下面表格为CONSTANT_Class_info型常量的结构

类型 名称 数量 作用
u1 tag 1 区分常量类型
u2 name_index 1 全限定名

0x0002:指向常量池中第二项

0x01CONSTANT_Utf8_info类型的常量

下面表格为CONSTANT_Utf8_info型常量的结构

类型 名称 数量 作用
u1 tag 1 区分常量类型
u2 length 1 UTF-8的字符串长度是多少字节
u1 bytes length 使用UTF-8缩略码表示的字符串

UTF-8缩略编码

  • \u0001\u007f(1~127的ASCII码)的用一个字节表示
  • \u0080\u07ff之间的所有字符用两个字节表示
  • \u0800\uffff之间的所有字符用三个字节表示

0x0009:UTF-8的字符串长度是9字节

0x74:十进制为116,查询ASC码表可知,为字母t

74 65 73 74 2F 54 65 73 74逐个翻译后为:test/Test,也就是文章开头源码的全限定名。


1
07 00 04 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74

0x07CONSTANT_Class_info,类或接口的引用

0x0004:常量池中第4项

0x01CONSTANT_Utf8_info型的常量

0x0010:UTF-8的字符串长度是16字节

6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74:按照十六进制转为十进制,再查找ASC码表可知内容为:java/lang/Object

由于Class文件中方法、字段等都需要引用CONSTANT_Utf8_info型常量来描述名称,所以CONSTANT_Utf8_info型常量的最大长度也就是Java中方法、字段名的最大长度,也就是length的最大值,即65535。所以Java程序中如果定义了超过64KB英文字符的变量或方法名,将无法编译。

验证

可以用JDK自带工具javap -v ClassName来检查手动翻译内容是否正确,如下图所示

访问标志

作用

  • 识别一些类或者接口层次的访问信息
    • 这个Class是类还是接口
      • 类:是否被声明为final
    • 是否定义为public类型
    • 是否定义为abstract类型

位置

常量池结束之后,紧跟着的两个字节代表访问标志(access_flags)

含义

名称 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否被声明为final,只有类可以设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语意,invokespecial指令的语意在JDK1.0.2发生过改变,为了区别这条指令使用哪种语意,JDK1.0.2之后便意出来的类的这个标志必须为真
ACC_INTERFACE 0x0200 标识这是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或者抽象来说,次标志值为真,其他类值为假
ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生的
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 标识这是一个枚举
ACC_MODULE(JDK1.10新增) 0x8000 标识这是一个模块,不是类或者接口

实例

Test.class是一个普通的Java类,不是借口、枚举或者注解,被public关键字修饰但没有被声明为finalabstract,并且它使用了JDK 1.8之后的编译器进行编译,因此它的ACC_PUBLICACC_SUPER标志应当为真,而ACC_FINALACC_INTERFACEACC_ABSTRACTACC_SYNTHETICACC_ANNOTATIONACC_ENUM这6个标志位应当为假,因此它的access_flags的值应当为:0x0001|0x0020=0x0021

参考资料

维基百科-Java Class File

Oracle-虚拟机规范