深入理解Java虚拟机 - 类文件结构

561 阅读4分钟

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

概述

本文没有深入讲解类文件结构,而是作为类加载机制的前门知识,因为类加载机制需要对类文件有一定的了解基础,如果你希望从本文中获取详细的类文件结构信息,可能本文不是很适合你。

无关性的基石

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

Class类文件的结构

任何一个Class文件都对应着唯一一个类或接口的定义信息。Class文件是一组以8位字节为单位的二进制流,各个数据项目严格按照顺序紧凑排列在Class文件内,中间没有任何分隔符,所以Class文件存储的几乎是程序运行时的必要数据。
Class文件采用类似于C语言结构体的伪结构来存储数据,伪结构体只有两种数据类型:无符号数和表。

  • 无符号数
    无符号数属于基本的数据类型,u1、u2、u4、u8分别代表1个字节、2个字节、4个字节、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值等

  • 表是由多个无符号数或其他表组成的复合数据类型,所有表都以_info结尾,表用于描述有层次关系的复合结构的数据。

魔数与Class文件版本

Class文件的头4个字节称为魔数,唯一作用是确定这个Class文件是否为能被虚拟机接受的Class文件。使用魔数而不是拓展名来标记Class文件是因为拓展名可以随时被修改。
紧接着魔数的4个字节存储的是Class文件的版本号。高版本的JDK可以向下兼容低版本的Class文件,而不可以运行以后版本的Class文件。

常量池

紧接着版本号的是常量池入口,常量池可理解为Class文件之中的资源仓库,它是Class文件中与其他项目关联最多的数据类型,也是占用Class文件空间最多的数据项目,同时也是Class文件中第一个出现的表类型数据项目。
由于常量池中常量池中常量的数量是不固定的,所以在常量池的入口放置一项u2类型的数据,用来记录常量池中常量的数量。
常量池主要存放两大类常量:字面量和符号引用。

  • 字面量
    字面量比较接近Java语言层面的常量,如文本字符串、final定义的常量等
  • 符号引用
    符号引用属于编译原理方面的概念,包括下面三类常量:
  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

Java代码在进行javac编译的时候,没有像C或C++那样有连接这一步骤,而是在虚拟机加载Class文件时候进行动态连接。也就是说,Class文件不会保存各个方法和字段的最终内存布局信息。因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法给虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时解析到具体的内存地址。(通俗地说,只有类在加载时,被分配具体的内存空间,符号引用才会解析到具体对应的内存地址,变为直接引用,否则Class文件只存在符号引用;或者说调用一个类变量或类方法时,我们需要知道其在内存中的具体位置,才可以调用成功,所以类没有加载时,是无法知道其内存地址的,符号引用是无法调用成功的)

类或接口表

  • tag tag是标记位用于区分常量类型。一下是14种常量类型代表的具体含义:

  • name_index
    name_index是一个索引值,指向常量池中一个CONSTANT_Utf8_info类型常量,此常量代表这个类或接口的全限定名。

其他的表如方法表、属性表等都类似,这里不一一细说。