JVM虚拟机 - Class类文件结构

JVM虚拟机 - Class类文件结构

概述

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎都是程序运行的必要数据。当遇到需要占用8位字节以上空间的数据项时,会按照高位在前的方式分割成若干个8位字节进行存储。

Class文件格式中只有两种数据类型:无符号数

  • 无符号数属于最基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。
  • 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,以“_info”结尾。表用来描述有层次关系的符合结构的数据。

整个Class文件本质上就是一张表。

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

class文件的组织结构

  1. 魔数
  2. 本文件的版本信息
  3. 常量池
  4. 访问标志
  5. 类索引
  6. 父类索引
  7. 接口索引集合
  8. 字段表集合
  9. 方法表集合
  10. 属性表集合

1. 魔数

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

魔数的表示是用16进制的数:0xCAFEBABE 来表示。

2. 本文件的版本信息

在魔数后面的4个字节存储的就是Class文件的版本号:

  • 第5、第6个字节是次版本号(Minor Version)
  • 第7、第8个字节是主版本号(Major Version)

Java版本号的计算

Java的版本号是从45开始的,JDK1.1之后的每个JDK大版本发布主版本号向上加1。

高版本的JDK能向下兼容以前版本的Class文件,但不能兼容以后版本的Class文件,即使文件格式并未发生变化,虚拟机也拒绝执行超过其版本号的Class文件。

3. 常量池

3.1. 常量池的概念

常量池可以理解为Class文件之中的资源仓库,他是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。

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

  • 字面量:接近于Java语言层面的常量概念,如文本字符串、声明为final的常量
  • 符号引用:就是我们定义的各种名字,包括下面三类:
    • 类和接口的全限定名
    • 字段的名称和描述符
    • 方法的名称和描述符

3.2. 常量池的特点

  1. 常量池长度不固定

    常量池的大小是不固定的,因此常量池开头放置一个u2类型的无符号数,用来存储当前常量池的容量。JVM根据这个值就知道常量池的头尾来。

    这个无符号数从1开始,不是通常的从0开始

  2. 常量池中的常量由二维表来表示

    常量池开头有个常量池容器计数器,接下来就全是一个个常量了,只不过常量都是由一张张二维表构成,除了记录常量的值以外,还记录当前常量的相关信息。

  3. Class文件之中的资源仓库

  4. Class文件结构中与其他项目关联最多的数据类型

  5. 占用Class文件空间最大的数据项目之一

3.3. 常量池中常量的类型

刚才介绍了,常量池中的常量大体上分为:字面值常量 和 符号引用。在此基础上,根据常量的数据类型不同,又可以被细分为14种常量类型。这14种常量类型都有各自的二维表示结构。每种常量类型的头1个字节都是tag,用于表示当前常量属于14种类型中的哪一个。

以CONSTANT_Class_info常量为例,它的二维表示结构如下:
CONSTANT_Class_info表

类型 名称 数量
u1 tag 1
u2 name_index 1

tag表示当前常量的类型(当前常量为CONSTANT_Class_info,因此tag的值应为7,表示一个类或接口的全限定名);

name_index表示这个类或接口全限定名的位置。它的值表示指向常量池的第几个常量。它会指向一个CONSTANT_Utf8_info类型的常量,它的二维表结构如下:
CONSTANT_Utf8_info表:

类型 名称 数量
u1 tag 1
u2 length 1
u1 bytes length

CONSTANT_Utf8_info表示字符串常量;

tag表示当前常量的类型,这里应该是1;

length表示这个字符串的长度;

bytes为这个字符串的内容(采用缩略的UTF8编码)

4. 访问标志

访问标志(access_flags)占用两个字节,这个标志用于识别一些类或者接口层次的访问信息:

  • 这个Class是类还是接口
  • 是否定位为public类型
  • 是否定义为abstract类型
  • 是否被声名为final

访问标志中一共有16个标志位可用,当前只用了8个,其他要求一律为0。

5. 类索引、父类索引和接口索引集合

类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,接口索引集合(interfaces)是一组u2类型的数据的集合。

Class文件由这三项数据来确定这个类的继承关系:

  • 类索引确定这个类的全限定名

  • 父类索引用于确定这个类的父类的全限定名

    java.lang.Object外,所有Java类的父类索引都不为0。

  • 接口索引集合描述这个类实现了哪些接口

    被实现的接口按接口顺序从左到右排列在接口索引集合中。

类索引、父类索引和接口索引集合都按顺序排列在访问标志之后,类索引和父类索引用两个u2类型的所引致指向一个类型为CONSTANT_Class_info的类描述符常量,该常量的bytes字段记录了本类、父类的全限定名。

由于一个类的接口可能有好多个,因此需要用一个集合来表示接口索引,它在类索引和父类索引之后。这个集合头两个字节表示接口索引集合的长度,接下来就是接口的名字索引。

6. 字段表集合

字段表用于描述接口或者类中生命的变量。字段包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count
  • access_flags

    字段的访问标志。

  • name_index

    本字段名称的索引,指向一个CONSTANT_Class_info类型的常量,存储了本字段的名字等信息。

  • descriptor_index

    字段和方法的描述符,用于描述本字段在Java中的数据类型等信息。

  • attributes_count

    属性表集合的长度。

  • attributes

    属性表集合。

全限定名

全限定名就是把类全名中的.替换成了/而已,为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个;来表示全限定名结束。

简单名称

简单名称就是指没有类型和参数修饰的方法或者字段名称。

描述符

描述符的作用是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。

在描述符中:

  • 基本数据类型以及代表无返回值的void类型都用一个大写字符来表示。
  • 对象类型则用字符L加对象的全限定名来表示。
标识字符 含义 标识字符 含义
B 基本类型byte J 基本类型long
C 基本类型char S 基本类型short
D 基本类型double Z 基本类型boolean
F 基本类型float V 特殊类型void
I 基本类型int L 对象类型,如Ljava/lang/Object
  1. 对于数组类型,每一唯独将使用一个前置的“[”字符来描述,如一个定义为java.lang.String[][]类型的二维数组,将被记录为:[[Ljava/lang/String]],一个整形数组int[]将被记录为:[I

  2. 对于描述方法,按照“先参数列表,后返回值的顺序描述”,参数列表按照参数的严格顺序放在一组小括号“()”内。例如:void inc()的描述符为:()V。方法java.lang.String.toString()的描述符为:()Ljava/lang/String

    方法int indexOf(char[] source, int sourceOffset, int sourceCount, char[] target, int targetOffset, int targetCount, int fromIndex)的描述符为:([CII[CIII)I

7. 方法表集合

在class文件中,所有的方法以二维表的形式存储,每张表来表示一个函数,一个类中的所有方法构成方法表的集合。
方法表的结构和字段表的结构一致,只不过访问标志和属性表集合的可选项有所不同。

类型 名称 数量
u2 access_flags 1
u2 name_index 1
u2 descriptor_index 1
u2 attributes_count 1
attribute_info attributes attributes_count

方法中的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面,属性表作为Class文件格式中最具拓展性的一种数据项目,在第8节中讲。

8. 属性表集合

在Class文件、字段表、方法表都可以携带自己的属性表集合,以用于描述某些场景专有的信息。

属性表集合对于各个属性表的顺序不再做严格要求,只要不与已有属性名重复即可。任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。

为了正确解析Class文件,虚拟机与定义了一些虚拟机实现应当能识别的属性,对于这些属性,它的名称需要从常量池中引用一个CONSTANT_Utf9_info类型的常量来表示,而属性值的结构则是完全自定义的。

一个符合规则的属性表应该有如下结构:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u1 info attribute_length

你可能感兴趣的:(java,java,JVM虚拟机,Class,类文件结构)