深入理解JVM:类文件结构

来自Wikioe
Eijux讨论 | 贡献2020年10月25日 (日) 01:36的版本 →‎常量池
跳到导航 跳到搜索


无关性的基石

   代码编译的结果从本地机器码转变为字节码,是存储格式发展的一小步,却是编程语言发展的一大步。


  • 无关性:即与平台无关,“一次编写,到处运行(Write Once,Run Anywhere)”;
  • 无关性的基石:字节码(Byte Code):
    各种不同平台的Java虚拟机,以及所有平台都统一支持的程序存储格式;


Java虚拟机不与包括Java语言在内的任何程序语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集、符号表以及若干其他辅助信息。

Java虚拟机提供的语言无关性.jpg

Class类文件的结构

   任何一个Class文件都对应着唯一的一个类或接口的定义信息,但是反过来说,类或接口并不一定都得定义在文件里(譬如类或接口也可以动态生成,直接送入类加载器中)。
  • Class文件是一组以8个字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符

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

  1. “无符号数”:基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
  2. “表”:由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。
    表用于描述有层次关系的复合结构的数据。
  • 整个Class文件本质上也可以视作是一张表。


Class文件格式:

Class文件格式.jpg

如上图,Class的没有任何分隔符,其数据项的顺序、数量,甚至于数据存储的字节序(Byte Ordering,Class文件中字节序为Big-Endian)等,都是被严格限定的,哪个字节代表什么含义,长度是多少,先后顺序如何,全部都不允许改变。


魔数、Class文件的版本

魔数(Magic Number):每个Class文件的头4个字节被称为魔数(Magic Number),唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。

  • 很多文件格式标准中都有使用魔数来进行身份识别的习惯,因为相对于文件后缀更加安全;(譬如图片格式,如GIF或者JPEG等在文件头中都存有魔数)
  • Class文件的魔数为:“0xCAFEBABE(咖啡宝贝?)”;


Class文件的版本号:紧接着魔数的4个字节存储的是Class文件的版本号;

  1. 第5和第6个字节是次版本号(MinorVersion)
  2. 第7和第8个字节是主版本号(Major Version)
  • Java的版本号是从45开始;
  • 高版本的JDK能向下兼容以前版本的Class文件,但不能运行以后版本的Class文件;
    如:JDK 1.1能支持版本号为45.0~45.65535的Class文件,无法执行版本号为46.0以上的Class文件,而JDK 1.2则能支持45.0~46.65535的Class文件。


示例: Java Class文件的结构.jpg 使用“WinHex”打开Class文件如上图:

  1. 开头4个字节的十六进制表示是 0xCAFEBABE
  2. 代表次版本号的第5个和第6个字节值为 0x0000,而主版本号的值为 0x0032,也即是十进制的50,该版本号说明这个是可以被JDK 6或以上版本虚拟机执行的Class文件。


Class文件版本号:

Class文件版本号.jpg

常量池

紧接着主、次版本号之后的是常量池入口,常量池可以比喻为Class文件里的资源仓库,它是Class文件结构中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一,另外,它还是在Class文件中第一个出现的表类型数据项目。


  • 由于常量池中常量的数量是不固定的,所以在常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count,从1而不是0开始的)。


常量池中主要存放两大类常量:

  1. 字面量(Literal):接近于Java语言层面的常量概念,如文本字符串、被声明为final的常量值等
  2. 符号引用(Symbolic References):属于编译原理方面的概念,主要包括下面几类常量:
    1. 被模块导出或者开放的包(Package)
    2. 类和接口的全限定名(Fully Qualified Name)
    3. 字段的名称和描述符(Descriptor)
    4. 方法的名称和描述符
    5. 方法句柄和方法类型(Method Handle、Method Type、Invoke Dynamic)
    6. 动态调用点和动态常量(Dynamically-Computed Call Site、Dynamically-Computed Constant)


  • 符号引用(如同之前内存部分所说):
    1. class文件中,方法调用其他方法,需要将其他方法的“符号引用”转化为直接引用,此过程即为“动态链接”;
    2. Java虚拟机栈的每个栈帧都有一个“指向运行时常量池中,该栈所属方法的”符号引用;
  • 常量池中每一项常量都是一个表:
    1. 共有17类表;
    2. 这17种常量类型各自有着完全独立的数据结构;
    3. 表结构起始的第一位是个u1类型的标志位(tag),代表着当前常量属于哪种常量类型:
      常量池的项目类型1.jpg
      常量池的项目类型2.jpg


常量池中的17种数据类型的结构总表:

常量池中的17种数据类型的结构总表1.jpg
常量池中的17种数据类型的结构总表2.jpg
常量池中的17种数据类型的结构总表3.jpg

访问标志

类索引、父类索引与接口索引集合

字段表集合

方法表集合

属性表集合

字节码指令简介

6.4.1 字节码与数据类型 6.4.2 加载和存储指令 6.4.3 运算指令 6.4.4 类型转换指令 6.4.5 对象创建与访问指令 6.4.6 操作数栈管理指令 6.4.7 控制转移指令 6.4.8 方法调用和返回指令 6.4.9 异常处理指令 6.4.10 同步指令

公有设计,私有实现

Class文件结构的发展