初探class文件
Java在设计之初就提出一个非常著名的口号:“一次编写,随处运行”。亦即Java编译生成的二进制文件能够在不做任何改变的情况下运行于多个平台。而这也是Java语言与平台无关性的由来。然而JVM却非跨平台的,不同平台的JVM表现的差异也已被封装完善起来。我们通过这些虚拟机加载和执行同一种平台无关的字节码,使得我们的源代码不用根据不同平台编译成不同的二进制可执行文件。这也正是Java历久弥新的原因所在。
class文件结构剖析
Java虚拟机规定用u1、u2、u4 三种数据结构来表示1、2、4字节无符号整数,相同类型的若干条数据集合用表(table)的形式存储。表是一个变长的结构,由代表长度的表头 n 和紧随着的 n 个数据项组成。class文件采用类似C语言的结构体来存储数据。具体描述如下:
classFile { |
【记住】
calss 文件由下面十个部分组成: 助记
魔数(Magic Number) My
版本号(Minor&Major Version) Very
常量池(Constant Pool) Cute
类访问标记(Access Flag) Animal
类索引(This Class) Turns
超类索引(Super Class) Savage
接口表索引(Interface) In
字段表(Field) Full
方法表(Method) Moon
属性表(Attributes) Areas
魔数 文件类型标识
class文件魔数: 0xCAFEBABE(咖啡宝贝)
虚拟机在加载类文件之前会先检验这4个字节,如果不是0xCAFEBABE,则会抛出 java.langClassFormatError异常。
版本号 当前Java版本
魔数之后的四个字节分别表示 副版本号(Minor Version) 和 主版本号(Major Version)
Java 版本 | Major Version |
---|---|
Java 1.4 | 48 |
Java 5 | 49 |
Java 6 | 50 |
Java 7 | 51 |
Java 8 | 52 |
Java 9 | 53 |
常量池 存储操作数索引位置
常量池分为两部分:
1)常量池大小(cp_info_counts)
2)常量池项(cp_info)集合:最多包含n-1个元素
JVM 虚拟机目前一共定义了14种常量项tag类型,具体如下:
类型 | tag值 |
---|---|
CONSTANT_Utf8_info | 1 |
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 |
UTF8 编码与 modified UTF-8 编码
UTF-8 实现原理
UTF-8 是一种变长编码方式,使用 1~4 个字节表示一个字符。规则如下:
- 1)对于传统的 ASCII 编码字符(0x0001~0x007F),UTF-8 用一个字节来表示,如下所示:
0000 0001 ~ 0000 007F -> 0xxxxxxx
因此 英文字母的 ASCII 编码和 UTF-8 编码的结果一样。
- 2)对于 0080~07FF 范围的字符,UTF-8 用2个字节表示,如下所示:
0000 0080 ~ 0000 07FF -> 110xxxx 10xxxxx
程序遇到这种字符的时候,会把第一个字节的110和第二个字节的10去掉,再把剩下的 bit 组成新的两字节数据。
- 3)对于 0000 0080~0000 FFFF 范围的字符,UTF-8 用3个字节表示,如下所示:
0000 0800 ~ 0000 FFFF ->1110xxxx 10xxxxx 10xxxxx
程序遇到这种字符的时候,会把第一个字节的1110、第二个和第三个字节的10去掉,再把剩下的 bit 组成新的3字节数据
- 4)对于 0001 0000~0010 FFFF 范围的字符,UTF-8 用4个字节表示,如下所示:
0001 0000 ~ 0010 FFFF ->11110xxx 10xxxxx 10xxxxx 10xxxxx
程序遇到这种字符的时候,会把第一个字节的11110、第二、第三、第四个字节的10去掉,再把剩下的位组成新的4字节数据
UTF-8 和 MUTF-8 区别
MUTF-8 里用两个字节表示空字符(”\0”),把前面介绍的双字节表示格式 110xxxxx 10xxxxxx 中的 x 全部填0,也即 0xC080;而在标准 UTF-8 标准编码中只用一个字节 0x00表示。这样处理的原因是在其他语言中(比如C语言)会把空字符当作字符串的结束,而 MUTF-8 这种处理空字符的方式保证字符串不会出现空字符,在 C语言 处理时不会意外截断。
MUTF-8 只用到了标准 UTF-8 编码中的单字节、双字节、三字节表示方法,没有用到四字节表示方式。编码 U+FFFF 之上的字符,Java 使用“代理对”(surrogate pair)通过2个字符表示,比如 emoji 表情 “😄” 的代理对为
\ud83d\ude02
.
类、字段、方法的访问标记
紧随常量池之后的区域是访问标记(Access flags),用来标识一个类为 final、abstract等,由两个字节表示,总共有16个标记为可供使用,然而目前只使用了其中8个。完整的类访问标记含义如下图所示: