Class 文件中的魔数、主次版本号与常量池

1. 前言

本节内容主要是介绍 Class 文件结构中的魔数、主次版本号与常量池。本节主要知识点如下:

  • Class 文件的数据类型,概念性的知识,为本节基础知识点;
  • Class 文件结构介绍,为本节次重点知识;
  • 魔数的定义及所占字节空间,为本节重点内容之一;
  • 次版本号与主版本号的定义及对照表,次版本号与主版本号为本节重点内容之一,版本号对照表为了解内容;
  • 常量池计数器与常量池的定义及意义,为本节重点内容之一。

2. Class文件数据类型

根据 Java 虚拟机规范的规定,Class 文件格式采用一种类似于 C 语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。

  • 无符号数:无符号数属于基本的数据类型,以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节;无符号数可以用来描述数字、索引引用、数量值或者按照 UTF-8 编码构成的字符串值;

  • :表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以“info”结尾。表用于描述有层次关系的复合结构的数据,整个 Class 文件本质上就是一张表。

Tips:无符号数和表这两种类型的数据,初次来看非常的抽象,从概念层面来看似乎很难理解。我们无需着急, 本节所讲述的魔数,次版本号,主版本号以及常量池计数器皆为无符号数类型,而常量池为表类型,讲解这些结构时,我会为大家提供示意图,使学习者从感官上看到这两种数据类型,从而彻底理解这两种数据类型。

3. Class 文件结构

Class 文件是一组以(8位bit的)byte 字节为基础单位的二进制流。如下图所示 Class 文件的字节码示意图:

图片描述

上图中被绿色框圈起来的则为标准的 Class 文件的样子。左侧为软件本身提供的辅助信息,记录当前行前面总共有多少个 byte (或者说多少个 u1 ),用于快速定位数据(通过数据偏移量的方式。右侧为直接以编辑器打开 Class 文件的样子,显示为乱码。

Tips:使用普通的编辑器打开 Class 文件我们会看到乱码,如果想要像上图一样观察 Class 文件的话,需要下载专门的编辑器。WinHex 就具备这个功能,有兴趣的同学可以安装 WinHex 并使用。

4. 魔数(Magic Number)

定义:每个 Class 文件的头 4 个字节(u4)称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接收的 Class 文件。所有 Class 文件,魔数均为 0xCAFEBABE。

Tips:从Class文件结构图中,我们可以看到,Class文件的开头确实是CAFEBABE。下载并安装 WinHex 的学习者,如果打开任意的一个Class文件,开头也必然是CAFEBABE。

无符号数结构示意图:前文提到,魔数是无符号数类型的数据,对于无符号数,我们通常以 u1、u2、u4、u8 来分别代表 1 个字节、2 个字节、4 个字节和 8 个字节。我们先来看下魔数的示意图:

图片描述

Tips:上文提到过,魔数开头为 CAFEBABE,占用 4 个字节,无符号数表示为 u4,那么其中 CA,FE,BA,BE分别占用 1 个字节,无符号数表示为 u1。至此我们了解了魔数的定义及作用,并明白了何为无符号数类型,掌握了魔数的无符号数表示为 u4。

5. 次版本号与主版本号

学前疑问:学习者可能会有疑问,为什么标题说的是次版本号与主版本号,次版本号在前,而主版本号在后呢?先次后主,是不是读起来感觉有点别扭呢?

疑问解答:特别强调下,对于Class 文件结构,第一部分为 u4 的魔数,魔数后边紧跟的就是 u2 的次版本号,次版本号后边才是 u2 的主版本号,此处需要特别注意,从结构上来说,次版本号在前,主版本号在后。

定义:次版本号与主版本号共同标识了我们所使用的的 JDK 版本,如 JDK 1.8.0 版本的次版本号为 u2 大小,用字节码表示为 00 00,主版本号也是 u2 大小,用字节码表示为 00 34。

  • 次版本号:JDK 版本的小版本号;
  • 主版本号:JDK 版本的大版本号。

Tips:如果 Class 文件的开头 8 个字节分别为 CA FE BA BE 00 00 00 34,那么我们可以确定,这是一个 JVM 可识别的 Class 文件,且使用的 JDK 1.8.0的版本进行的编译,因为前4个字节魔数为 CA FE BA BE 符合标准,后4 个字节 00 00 00 34 为 JDK 1.8.0的版本。

无符号数结构示意图:前文提到,次版本号与主版本号也是无符号数类型的数据。我们接下来看下魔数的示意图:

图片描述

Tips:至此我们了解了先次后主,了解了次版本号与主版本号分别占用 2 个字节,无符号数表示为 u2。同时我们从整体上了解了,魔数后边为次版本号,次版本号后边为主版本号。主版本号后边紧跟的是什么?不要着急,我们继续学习。

版本号对照表:开篇的前言部分已经说过,对照表部分为了解内容,这里简单举出几个版本的对照表,了解一下即可。

JDK 版本 16进制字节码
1.8.0 00 00 00 34
1.7.0 00 00 00 33
1.6.0 00 00 00 32
1.5.0 00 00 00 31

6. 常量池计数器与常量池

Tips:前文提出过,主版本号后边紧跟的是什么,现在我们揭开答案,主版本号后边紧跟的是常量池计数器,常量池计数器后边紧跟的是常量池。那么常量池后边紧跟的是什么?此处又提出问题,我们后续讲解会有解答。

定义:我们先来看下两者的定义。

  • 常量池计数器:记录常量池中的常量的数量。由于常量池中的常数的数量是不固定的,所以在常量池的入口放置了一个 u2 类型的数据,来代表常量池容器记数值(constant_pool_count)。常量池计数器也是无符号数类型数据。
  • 常量池:Class 文件中的资源仓库,它是 Class 文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最多的数据项目之一,同时它还是 Class 文件中第一个出现的表类型数据项目。

Tips:相信学习者对常量池的兴趣会比较大,为什么这么说呢?从常量池定义中,我们看到了一句话:“它还是Class文件中第一个出现的表类型数据项目”。表类型数据,终于等到了 Class 文件的表类型数据结构,我们本节会为学习者提供表类型的机构示意图。

常量池计数器无符号数结构示意图:我们还是要按照 Class 文件的结构顺序一步一步来说,先要搞明白常量池计数器,然后再去学习表类型的常量池。

图片描述

常量池计数器,我们对于这种无符号数结构其实已经非常的了解了,所以此处我们点到即止,了解常量池计数器的定义及作用,了解了常量池计数器占用 u2 大小即可。

常量池表结构示意图:我们终于接触到了 Class 文件中的表结构,那么我们先睹为快,然后再讲解常量池的重要知识点。

图片描述

常量池中存储的数据:常量池中主要存放着两种常量,字面量(Literal)和符号引用(Synbolic References)。

  • 字面量包括:文本字符、声明为 final 的常量值、基础数据类型的值等;
  • 符号引用包括:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符。

cp_info类型:cp_info 又可细分为 14 种结构类型。下表中第二列所说的标志,是指每一种数据类型的标记值,此处做简单了解即可。
图片描述

7. 小结

本节讲解了 Class 文件结构开头的 3 种结构,魔数,次版本号与主版本号,常量池计数器与常量池。我们了解了它们的定义及意义,也了解了什么是无符号类型数据,什么是表类型数据,我们需要进行重点掌握。

本节我们也抛出了问题,常量池后边紧跟的结构是什么?我们会在下篇课程中进行讲解。本节所了解到的无符号数与表类型数据,有助于我们后续对其他 Class 文件结构的学习。