类加载机制(1):类文件结构

0 阅读3分钟

编译的结果

在操作系统课上我们都学过,程序会被编译器翻译为由0和1构建的二进制格式,但是随着虚拟机以及大量建立在虚拟机上的程序语言的出现,将编写的程序编译成为二进制本地机器码已经不是唯一的选择了,越来越多的语言选择了与操作系统和机器指令集无关的平台中立的格式作为程序编译后的存储格式

image.png

Class类文件的结构

任何一个Class文件都对应着唯一的一个类或者接口定义信息,但是,类或者接口并不一定都得定义在文件中,它们也可以动态生成,直接送入类加载器中。

Class文件是一组以8字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件中。根据《Java虚拟机规范》的规定,Class文件格式采用一种类似于C语言结构体伪结构来存储数据,这种伪结构中只有两种数据类型:“无符号数”和“表”:

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

无论是无符号数还是表,当需要描述同一类型数量不定的多个数据时,经常会使用一个前置的容量计数器若干个连续的数据项的形式,这时候称这一系列连续的某一类型的数据为某一类型的“集合”。

魔数和Class文件的版本

魔数(Magic Number)是指在计算机编程或文件格式中,用于标识特定文件类型或数据结构的固定数值或字节序列。它通常位于文件的开头,帮助系统和程序快速识别文件格式

每个Class文件的头4个字节(0xCAFEBABE)被称为魔数,它的作用是确定这个文件是否为一个能够被虚拟机接收的Class文件。

在魔数之后的四个字节是Class的版本号:第5和6字节是次版本号,第7和8字节是主版本号。

下面给一个示例:

public class TestClassFile {
    private int m;
    public int inc(){
        return m++;
    }

    public static void main(String[] args) {

    }
}

这段代码的字节码如下: image.png

常量池

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

常量池中的常量数量不固定,因此在常量池入口放置一项u2类型数据,代表常量池容量计数器,比如下图中的1A代表有26个常量。

常量池中有两大类常量:字面量符号引用

Java代码在进行Javac编译的时候,并不像C/C++那样有连接这一步,而是在虚拟机加载Class文件的时候进行动态连接,因此在Class文件中不会保存各个方法、字段在内存中的布局信息,而是以字符串的形式引用比如以下这些:

  1. 类/接口:CONSTANT_Class_info -> "java/lang/Object"
  2. 字段: CONSTANT_Fieldref_info -> "java/lang/System.out:Ljava/io/PrintStream;"
  3. 方法: CONSTANT_Methodref_info -> "java/io/PrintStream.println:(Ljava/lang/String;)V"