1. 字节码
Java字节码是Java虚拟机执行的一种指令格式.class文件是编译器编译之后供虚拟机解释执行的二进制字节码文件.
下面举个例子,写一段Java打码,并编译.
package com.xfhy.test;
public class Hello {
private int num = 1;
public int add() {
num = num + 2;
return num;
}
}
编译得到class文件之后,用Hex Fiend软件打开该class文件.
CAFEBABE 00000034 00130A00 04000F09 00030010 07001107 00120100 036E756D
01000149 0100063C 696E6974 3E010003 28295601 0004436F 64650100 0F4C696E
654E756D 62657254 61626C65 01000361 64640100 03282949 01000A53 6F757263
6546696C 6501000A 48656C6C 6F2E6A61 76610C00 0700080C 00050006 01001363
6F6D2F78 6668792F 74657374 2F48656C 6C6F0100 106A6176 612F6C61 6E672F4F
626A6563 74002100 03000400 00000100 02000500 06000000 02000100 07000800
01000900 00002600 02000100 00000A2A B700012A 04B50002 B1000000 01000A00
00000A00 02000000 03000400 04000100 0B000C00 01000900 00002B00 03000100
00000F2A 2AB40002 0560B500 022AB400 02AC0000 0001000A 0000000A 00020000
0007000A 00080001 000D0000 0002000E
class文件内部就是长这个样子. 里面是一堆16进制字节,完全看不懂.JVM是如何解读的?
2. 字节码结构
class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构只有两种数据类型: 无符号数和表.
- 无符号数: 无符号数可以用来描述数字、索引引用、数量值或按照utf-8编码构成的字符串值.其中无符号数属于基本的数据类型。以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节.
- 表: 表是由多个无符号数或其他表构成的复合数据结构.所有的表都以“_info”结尾, 由于表没有固定长度,所以通常会在其前面加上个数说明.
class的内容其实就是下面这张表里面的数据顺序排列的,只需要安装这个顺序逐一进行解读就可以了:
类型 | 名称 | 说明 | 长度 |
---|---|---|---|
u4 | magic | 魔数,识别 Class 文件格式 | 4 个字节 |
u2 | minor_version | 副版本号 | 2 个字节 |
u2 | major_version | 主版本号 | 2 个字节 |
u2 | constant_pool_count | 常量池计算器 | 2 个字节 |
cp_info | constant_pool | 常量池 | n 个字节 |
u2 | access_flags | 访问标志 | 2 个字节 |
u2 | this_class | 类索引 | 2 个字节 |
u2 | super_class | 父类索引 | 2 个字节 |
u2 | interfaces_count | 接口计数器 | 2 个字节 |
u2 | interfaces | 接口索引集合 | 2 个字节 |
u2 | fields_count | 字段个数 | 2 个字节 |
field_info | fields | 字段集合 | n 个字节 |
u2 | methods_count | 方法计数器 | 2 个字节 |
method_info | methods | 方法集合 | n 个字节 |
u2 | attributes_count | 附加属性计数器 | 2 个字节 |
attribute_info | attributes | 附加属性集合 | n 个字节 |
2.1 魔数
用于标记当前文件是class(为啥不是用后缀来标记该文件为class文件,因为防止后缀被修改,为了安全),固定值为0XCAFEBABE.文件一开始就是这个.
2.2 版本号
魔数后面的00000034
是版本号,也是4个字节,其中前2个字节表示副版本号,后2个字节表示主版本号.这里0034对应的值是52,也就是jdk 1.8.0
2.3 常量池
2.3.1 常量池容量计数器
接着是常量池相关的东西了,常量池的数量不固定,需要2个字节来表示常量池容量计数值.demo里面是0013
,也就是19.
咱通过javap -verbose Hello
命令查看该class的字节码如下(只截取了常量池部分数据):
Constant pool:
#1 = Methodref #4.#15 // java/lang/Object."":()V
#2 = Fieldref #3.#16 // com/xfhy/test/Hello.num:I
#3 = Class #17 // com/xfhy/test/Hello
#4 = Class #18 // java/lang/Object
#5 = Utf8 num
#6 = Utf8 I
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 add
#12 = Utf8 ()I
#13 = Utf8 SourceFile
#14 = Utf8 Hello.java
#15 = NameAndType #7:#8 // "":()V
#16 = NameAndType #5:#6 // num:I
#17 = Utf8 com/xfhy/test/Hello
#18 = Utf8 java/lang/Object
可以看到这里其实序号是从1开始的,而且总共是18个??? 那为啥class文件里面的数值是19?因为它把第0项常量空出来了:这是为了在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达"不引用任何一个常量池项目"的含义,这种情况可用索引值0来表示.
2.3.2 常量解读
首先是第一个常量0x0a
,即10.这里的10代表的是CONSTANT_Methodref_info
,即类中方法的符号引用. 常量标志数值的含义表:
类型 | 标志 | 描述 |
---|---|---|
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_MothodType_info | 16 | 标志方法类型 |
CONSTANT_InvokeDynamic_info | 18 | 表示一个动态方法调用点 |
什么是符号引用? 常量池主要存放两大常量,字面量
和符号引用
.
- 字面量: 文本字符串,声明为final的常量值
- 符号引用: 类和接口的全限定名,字段的名称和描述符,方法的名称和描述符
知道了该标志的含义,说明接下来的数据就是类中方法的符号引用的数据.但是我们不知道这个数据到底有多长.得看下面这个表格,常量池中的17种数据类型的结构总表,才知道它的结构到底如何:
从表中查出CONSTANT_Methodref_info
的tag是10,上面已经拿到了.然后接下来的2个u2表示它的数据,在demo中的值为: 0004 000F
- 前两位的值为
0x0004
,即 4,指向常量池第 4 项的索引 - 后两位的值为
0x000f
,即 15,指向常量池第 15 项的索引
至此,第一个常量就解读完毕了.后面还有17个常量,就不一一解读了,就是查字典.所有的常量都在这里了,它们最后的解读出来是和javap -verbose Hello
解读出来的Constant pool
是一致的.
0A00 04000F09 00030010 07001107 00120100 036E756D
01000149 0100063C 696E6974 3E010003 28295601 0004436F 64650100 0F4C696E
654E756D 62657254 61626C65 01000361 64640100 03282949 01000A53 6F757263
6546696C 6501000A 48656C6C 6F2E6A61 76610C00 0700080C 00050006 01001363
6F6D2F78 6668792F 74657374 2F48656C 6C6F0100 106A6176 612F6C61 6E672F4F