大学时有个段子:什么是最牛逼的程序猿?
最牛程序猿深情地注视着屏幕上一堆16进制,露出满足而诡魅的微笑。
同学问他在看啥,他小声说:蒼井そら。
好,下面,我们也来学习阅读一下16进制版的Class文件。
1.源头:Class规范
每一个Java版本发布时,会公布两份规范文档(以Java8为例):
Java语言规范:《The Java Language Specification, Java SE 8 Edition》
Java虚拟机规范:《The Java Virtual Machine Specification, Java SE 8 Edition》
Java虚拟机规范中有很大篇幅就是描述Class文件格式的规格,它也是帮助我们学习、理解Class文件最好的参考资料。
2.验证:演练代码
为了更好地覆盖JVM规范中描述的Class文件格式主脉络,笔者设计了如下示例代码:
这段代码体现了5种Java的语法特征:
类:Demo2
成员变量:私有成员变量filed1,int类型,初始值为1
方法:公有方法hello,无参数无返回值
局部变量:局部变量i,int类型,初始值为1
语句:打印语句println,打印内容为hello world
使用javac编译上述代码,我们会得到Demo2.class文件,用16进制编辑器打开如下:
为了更清晰的查看,我们将上述数据复制到Excel表格中,着色
3.合法性检查依据:魔数、版本号
首先,通过阅读规范,我们获得如下知识:
魔数:4个字节
副版本号:2个字节
主版本号:2个字节
然后,在IDE中将魔数标记出来:
然后,在IDE中将版本号标记出来:
- 分析
魔数值和版本号是JVM加载Class文件时的合法性检查依据:
如果16进制文件内容的开始不是CA FE BA BE,则JVM就认为这个文件不是合法的Class文件;
如果16进制文件内容的版本号与JVM版本号不匹配,则JVM就拒绝加载。00 00 00 34,表示Java8对应的JVM可以加载。
- 技术直男的浪漫
这里值得提到一个趣闻:魔数值CA FE BA BE,应该这样断句:CAFE BABE。
Gosling,这位资深技术直男,就是这样温情地形容,他发明的这个编程语言——咖啡宝贝。
4.信息仓库:常量池
4.1.常量池计数器
在笔者前一篇文章《【编译引擎】学习阅读Class文件结构的意义》中提到:
常量池:本质是一张表格。
常量:是站在JVM角度看,公用的一些常量值,为了压缩Class文件的大小而存在。
因此,JVM规范才规定Class文件中,有2个字节的常量池计数器(表示常量表格的长度),紧接着的N个字节存储M个常量的具体内容
M个常量:M=常量池计数器的值 - 1
N个字节:N是变长的,因为每个常量类型不同,有的常量需要4个字节存储,有的常量需要很多字节存储。
- 首先,找到常量池计数器=00 24,转换为10进制是36,说明常量池中存储了35个常量。
一个规范中的细节:The
constant_pool
table is indexed from 1 toconstant_pool_count
- 1.这段话的意思是,常量池计数器值-1就是常量个数
4.2.N种常量类型
接下来,我们解读具体的常量,前文讲过,常量有不同的类型,那么Class文件如何表达常量类型呢?
规范中,采用了如下结构表示不同类型的常量:
那么,JVM支持多少种类型的常量呢?
本文的演练代码中,涉及到了种类型的常量:
CONSTANT_Utf8:1
CONSTANT_String:8
CONSTANT_Class:7
CONSTANT_Fieldref:9
CONSTANT_NameAndType:12
CONSTANT_Methodref:10
4.3.CONSTANT_Utf8类型的常量
JVM规范:The
CONSTANT_Utf8_info
structure is used to represent constant string values
这种类型的常量,就是表达字符串字面量。
它的结构如下:
我们以第25个常量为例,尝试理解一下:
这个常量值=01 00 0B 68 65 6C 6C 6F 20 77 6F 72 6C 64,如下图所示的含义
第25个常量,表示的就是演练代码中的打印的字符串内容:
4.4.CONSTANT_String类型的常量
JVM规范:The
CONSTANT_String_info
structure is used to represent constant objects of the typeString
:
这种类型的常量,是字符串对象类型的,它的值指向4.3中描述的字符串字面量。
它的结构如下:
我们以第5个常量为例,尝试理解一下:
这个常量的值= 08 00 19,如下图所示的含义:
结合第5个常量和第25个常量,再看演练代码,我们窥见JVM处理字符串的原理:
4.5.CONSTANT_Class类型的常量
JVM规范:The
CONSTANT_Class_info
structure is used to represent a class or an interface
这种类型的常量,表示类或者接口
它的结构如下:
我们以第6个常量为例,尝试理解一下:
这个常量的值= 07 00 1C,结合第28个常量,如下图所示的含义:
再看演练代码,CONSTANT_Class类型的常量表达了演练代码中定义的Demo2类:
4.6.CONSTANT_Fieldref类型的常量
JVM规范:Fields, methods, and interface methods are represented by similar structures:
这种类型的常量,表示字段(例如:一个类中的成员变量)。JVM规范还告诉我们,实例方法、接口方法都是同样的Class格式。
我们以第2个常量为例,尝试理解一下:
这个常量的值= 09 00 06 00 16,结合第6个常量和第22个常量,如下图所示的含义:
但是第22个常量=0C 00 08 00 09表示了什么呢?我们看下一章节的解读
4.7.CONSTANT_NameAndType类型的常量
JVM规范:The
CONSTANT_NameAndType_info
structure is used to represent a field or method, without indicating which class or interface type it belongs to
这种类型的常量,表示一个字段的类型信息
我们以第22个常量为例,尝试理解一下:
这个常量的值= 0C 00 08 00 09,结合第8个常量和第9个常量,如下图所示的含义:
至此,我们就看到了演练代码中在JVM中如何表达int field1
信息了:
4.8.CONSTANT_Methodref类型的常量
JVM规范:Fields, methods, and interface methods are represented by similar structures:
这种类型的常量,表示方法。JVM规范还告诉我们,它的结构和字段类型常量都是同样的Class格式。
我们以第5个常量为例,尝试理解一下:
这个常量的值= 0A 00 1A 00 1B,结合第26、27个常量,如下图所示的含义:
5.总结:常量池构建的静态全局观
根据演练代码产生的Class文件,JVM构建了35个常量:
我们以图形化的方式进一步看看,Class文件的常量池到底为JVM构建了怎样的信息树?
5.1.源代码中有哪些方法引用
常量池构建了方法引用的信息树,我们可以看到两颗方法引用树,如下图:
以其中一棵树为例,遍历这棵树,我们可以得到如图的信息:
源代码中有一个方法,此方法属于Object类,方法名为
,方法无输入参数,无返回
5.2.源代码中有哪些字段引用
常量池构建了字段引用的信息树,我们可以看到两颗字段引用树,如下图:
以其中一棵树为例,遍历这棵树,我们可以得到如图的信息:
源代码中有一个字段,此字段属于Demo2类,字段名为field1,字段数据类型为int
5.3.源代码中有哪些字符串对象
常量池构建了字符串类型对象的信息树,我们可以看到一颗字符串对象树,如下图:
以这棵树为例,遍历这棵树,我们可以得到如图的信息:
源代码中有一个字符串对象,此字符串对象的值是hello world
5.4.源代码中其它的字符串字面量
5.5.静态的全局观
至此,我们已经看到了常量池为JVM诠释了Java源代码中的信息树。
常量池中的这些信息树,为JVM构建出了一个静态的全局观。
通过这些信息树,我们已经能反演出Java源代码中的静态结构:
有几个类?
类中有几个成员变量?
类中有几个方法?
每个成员变量的名称、数据类型
每个方法的名称、输入参数列表、返回值类型
但常量池中还有一些信息孤点,我们无法反演出一些动态结构:
成员变量的状态变化过程
方法体内的运行过程
方法内局部变量的变化过程
6.下一步
笔者下篇会继续阅读本演练代码对应的Class文件剩余部分,包括
访问标识
类索引
字段表
方法表
属性表
进一步理解Class字节码如何为JVM构建动态全局观。
**高能预警:**如果您能耐心地看到这句话,恭喜您,距离”看着16进制享受而诡魅的微笑“又近了一步,加油!