JVM的故事——类文件结构

类文件结构

文章目录


一、概述

计算机是只认由0、1组成的二进制码的,不过随着发展,我们编写的程序可以被编译成与指令集无关、平台中立的一种格式。

二、无关性基石

对于不同平台和不同平台的Java虚拟机,都支持一种程序存储格式---字节码。Java虚拟机并不与Java这个语言绑定,它只与class文件绑定。任何语言编译后生成的class文件都可以在Java虚拟机上运行。

三、Class类文件的结构

Java语言一直保持着良好的向后兼容性,Class文件结构的稳定功不可没。在Java语言经历了许多的改进和更新,Class文件的结构和功能几乎没有变化,只有一些新增和补充内容。

任何一个class文件都对应着唯一的一个类或者接口的定义信息,但并不一定每个类或者接口都有着对应的class文件(可以动态生成,直接进入类加载器中)。

Class文件是一组以8个字节为基础单位的二进制流,中间没有任何的分隔符。当遇到大于8个字节的空间存储时,会按照高位在前把变量分割成若干个8个字节进行存储。Class文件存储数据只通过两种结构:无符号数和表。u1、u2、u8代表1、2、8个字节的无符号数。表是由多个无符号数或者其它表构成的结构,表的命名都习惯性地以"_info"结尾。整个class文件也可以看成一张表,表的组成结构如下图。

1、魔数与Class文件的版本

每个class文件的头四个字节是魔数,这是用来辨别该class文件是否可以被虚拟机接受。许多类型的文件都有魔数,例如gif、jpg等。使用魔数比使用后缀名辨别文件格式更加安全,因为后缀名是可以更改的。文件格式的魔数由制定者自己定义,class文件的魔数是0XCAFEBABE

紧接着魔数后面的四个字节是class文件的版本号定义,第五六字节定义的是次版本号(minor version),第七八字节定义的是主版本号(major version)。Java的class文件版本号是从45开始的,每个Java的大版本发布,主版本号就加一。高版本JDK可以兼容低版本的class文件。

分析此图,由5、6位是0x0000可以得出次版本号为0,由7、8位是0x0032可以得出主版本号是50,所以该class文件版本号是50.0,对应的JDK版本应该是JDK1.6,JDK1.6能支持版本号为45.0-50.65535的class文件。

2、常量池

在主次版本号之后的就是常量池,它是class文件中的资源仓库,占用着大量的数据,也是第一个出现的表类型数据项。

常量池的入口需要放置一个u2类型的数据,来表示有多少常量。这个容量的计数是从1开始而不是从0开始的

如此图,可以看见偏移地址0x00000008处,值为0x0016,即常量池的容量为22,那么就有21个常量,索引为1-21。把0空出来是为了表达不引用任何一个常量来设计的。不过除了常量池的其它集合,索引还都是从0开始的。

常量池中的每一个常量都是一个表,截止JDK13,常量表中一共有17种不同类型的常量。这些常量表都有一个相同的特点,就是它们的第一位是u1类型的标志位。

下面开始对常量进行分析

第0x00000008偏移位说明了有21个常量。从0x0000000A开始,0x07是第一个常量的标志位(tag),根据6-3常量池的项目类型可以得知这是一个类或接口的符号引用

根据表6-4就可以知道接下来两个字节要表示这个常量的name_index,即常量池的索引值。它指向一个CONSTANT_Utf8_info类型常量,这个常量代表类的全限定名。从偏移位0x0000000B开始,可以知道该常量的索引值为0x0002,即指向第二个常量。

那么就从0x0000000D开始,看第二个常量,标志位为0x01,由表6-3可知该常量是UTF-8编码的字符串。这里使用的是UTF-8缩略编码,区别是:从'\u0001'到'\u007f'之间的字符(相当于1~127的ASCII码)的缩略编码使用一个字节表示, 从'\u0080'到'\u07ff'之间的所有字符的缩略编码用两个字节表示,从'\u0800'开始到'\uffff'之间的所有字符 的缩略编码就按照普通UTF-8编码规则使用三个字节表示。(其实就是省略所有前面的0)

由表6-5,可以从偏移位0x0000000E开始,看该字符串的length,为0x001D,即29。往后29 个字节正好都在1~127的ASCII码范围以内,内容为"org/fenixsoft/clazz/TestClass

可以使用javap指令输出常量表,由输出的常量对比,发现分析的两个常量都是正确的。

像常量表中的如"I""V"""这些,在程序中并没有,它们是由编译器自动生成的,是用来描述一些不方便用固定字节描述的内容的。

3、访问标志

在常量池结束后,后面两个字节是访问标志。访问标志代表着该类或接口的访问信息

比如6-1中的代码,TestJava是一个普通的Java类。它被public修饰,并且使用了JDK1.2之后的编译器进行编译,所以它的access_flag应该是0x0001|0x0020=0x0021


相关推荐
程序员zgh4 小时前
C语言 指针用法与区别(指针常量、常量指针、指针函数、函数指针、二级指针)
c语言·开发语言·jvm·c++
没有bug.的程序员4 小时前
熔断、降级、限流:高可用架构的三道防线
java·网络·jvm·微服务·架构·熔断·服务注册
风景的人生9 小时前
一台电脑上可以同时运行多个JVM(Java虚拟机)实例
java·开发语言·jvm
五阿哥永琪11 小时前
JVM 类加载的过程&类加载器&双亲委派机制
jvm
想学后端的前端工程师12 小时前
【Java JVM虚拟机深度解析:从原理到调优】
java·jvm·python
oioihoii12 小时前
C++多线程中join与detach机制深度解析
java·jvm·c++
最贪吃的虎12 小时前
JVM扫盲:内存模型
java·运维·jvm·后端
图乐aj12 小时前
运维工程师技能之JVM
运维·jvm
郝学胜-神的一滴13 小时前
Linux 多线程编程:深入理解 `pthread_join` 函数
linux·开发语言·jvm·数据结构·c++·程序人生·算法
代码or搬砖13 小时前
== 和 equals() 的区别
java·开发语言·jvm