- 魔数
- 版本
- 常量池
- 访问标志
示例源码:
1 | package test; |
示例Class文件:
1 | CA FE BA BE(Class魔数) |
魔数
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)
- 方法的名称和描述符
- 字面量(Literal):比较接近Java语言层面的常量概念
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 |
0x07
:CONSTANT_Class_info
,类或接口的引用
下面表格为CONSTANT_Class_info型常量的结构
类型 | 名称 | 数量 | 作用 |
---|---|---|---|
u1 | tag | 1 | 区分常量类型 |
u2 | name_index | 1 | 全限定名 |
0x0002
:指向常量池中第二项
0x01
:CONSTANT_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 |
0x07
:CONSTANT_Class_info
,类或接口的引用
0x0004
:常量池中第4项
0x01
:CONSTANT_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
类型 - …
- 这个Class是类还是接口
位置
常量池结束之后,紧跟着的两个字节代表访问标志(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
关键字修饰但没有被声明为final
或abstract
,并且它使用了JDK 1.8之后的编译器进行编译,因此它的ACC_PUBLIC
、ACC_SUPER
标志应当为真,而ACC_FINAL
、ACC_INTERFACE
、ACC_ABSTRACT
、ACC_SYNTHETIC
、ACC_ANNOTATION
、ACC_ENUM
这6个标志位应当为假,因此它的access_flags
的值应当为:0x0001|0x0020=0x0021