《深入理解Java虚拟机》(第三版)读书笔记(四):第六章 类文件结构

《深入理解Java虚拟机》(第三版)读书笔记(四):第六章 类文件结构

​ ​ ​ ​ ​ ​ ​ 原书第三版的第五章是调优案例分析与实战,我是这样想的,把非理论偏实战的内容单独整理下,把理论部分的内容先啃完然后再动手试试,所以读书笔记直接到了第六章的部分,包括后面的博客如果涉及到书中的实战部分可能会跳过相应的章节。

Class类文件的结构

​ ​ ​ ​ ​ ​ ​ Class文件是一组以8个字节为基础单位的二级制六,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。

​ ​ ​ ​ ​ ​ ​ 根据《Java虚拟机规范》的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:“无符号数”和“表”。下面对这两个概念做一下解释:

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

  • 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了便于区分,所有表的命名都习惯性地以“_info”结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上也可以视作是一张表,这张表由表6-1所示的数据项按严格顺序排列构成。

    下面来看一下Class文件的格式:

    ClassFile { 
        u4 magic;  // 魔法数字,表明当前文件是.class文件,固定0xCAFEBABE
        u2 minor_version; // 次版本号
        u2 major_version; //主版本号
        u2 constant_pool_count; // 常量池计数
        cp_info constant_pool[constant_pool_count-1];  // 常量池内容
        u2 access_flags; // 类访问标识
        u2 this_class; // 当前类
        u2 super_class; // 父类
        u2 interfaces_count; // 实现的接口数
        u2 interfaces[interfaces_count]; // 实现接口信息
        u2 fields_count; // 字段数量
        field_info fields[fields_count]; // 字段表集合,包含的字段信息 
        u2 methods_count; // 方法数量
        method_info methods[methods_count]; // 包含的方法信息
        u2 attributes_count;  // 属性数量
        attribute_info attributes[attributes_count]; // 各种属性
    }
    

​ ​ ​ ​ ​ ​ ​ Class文件的格式是严格限定的,比如说哪个字节代表什么意思,长度是多少,先后顺序怎么样,这些都不允许被改变。

魔法数字和Class文件的版本

​ ​ ​ ​ ​ ​ ​ Magic Number的作用是确定这个文件是否为一个能被虚拟机接收的Class文件。minor_version和major_version分别是次、主版本号。Java的版本号从45开始,每个JDK大版本发布,主版本号就会+1.

常量池

​ ​ ​ ​ ​ ​ ​ 常量池是Class文件结构中与其它项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一,另外,它还是在Class文件中第一个出现的表类型数据项目。

​ ​ ​ ​ ​ ​ ​ 由于常量池中常量的数量不固定,所以在常量池的入口放置一项u2类型的数据,代表常量池计数constant_pool_count。

​ ​ ​ ​ ​ ​ ​ 常量池中主要存放两大类常量,字面量和符号引用。字面量比如说文本字符串、被声明为final的常量值等等,符号引用属于编译原理方面的概念,主要包括:

  • 被模块导出或者开放的包
  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符
  • 方法句柄和方法类型
  • 动态调用点和动态常量

​ ​ ​ ​ ​ ​ ​ Java代码在进行Javac编译的时候,在虚拟机加载Class文件的时候进行动态连接,也就是说在Class文件中不会保存各个方法、字段最终在内存中布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话是无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机做类加载的时候,将会从常量池获得对应的符号引用,再在类创建的时候或运行时解析、翻译到具体的内存地址之中。

​ ​ ​ ​ ​ ​ ​ 常量池中每一项常量都是一个表,表结构起始的第一位是个u1类型的标志位。

访问标志

​ ​ ​ ​ ​ ​ ​ access_flags用于识别一些类或者接口层次的访问信息,包括这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等等。

​ ​ ​ ​ ​ ​ ​ 一共16个标志位可以使用,目前定义了9个,没用到的一律为0。

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

​​ ​ ​ ​ ​ ​ ​ 类索引(this_class)和父类索引(super_class)都是一个u2类型的数据,而接口索引集合(interfaces)是一组u2类型的数据的集合,Class文件中由这三项数据来确定该类型的继承关系。父类所以只有一个,除了java.lang.Object之外,所有的Java类都有父类,因此除了它之外,Java类的父类索引都不为0。

​ ​ ​ ​ ​ ​ ​ 对于接口索引集合,入口的第一项u2类型的数据为接口计数器interfaces_count,表示索引表的容量,如果没实现任何接口,那么值为0。

字段表集合

​ ​ ​ ​ ​ ​ ​ 用来描述接口或者类中声明的变量。Java语言中的字段包括类级变量以及实例级变量,不包括在方法内部声明的局部变量。下面是字段表的组成结构(图源网络):

img

​ ​ ​ ​ ​ ​ ​ 针对上面这些字段表示,整理出的字段表结构如下所示:

img

​ ​ ​ ​ ​ ​ ​ field_info由访问标值、名称索引、描述索引、属性表集合组成。书上关于access_flags也列举了一些访问标志,为了方便理解和记忆,摘取了LouLuan博主中的一张图,如下:

img

​ ​ ​ ​ ​ ​ ​ name_index和descriptor_index是对常量池项的引用,分别代表着字段的简单名称以及字段和方法的描述符。描述符是描述字段的数据类型、方法的参数列表和返回值。

​ ​ ​ ​ ​ ​ ​ 字段表集合中不会列出从父类或者父接口中继承而来的字段,但有可能出现原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,编译器就会自动添加指向外部类实例的字段。另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于Class文件格式来讲,只要两个字段的描述符不是完全相同,那字段重名就是合法的。

方法表集合

​ ​ ​ ​ ​ ​ ​ 方法表和字段表类似,包括访问标志、名称索引、描述符索引、属性表集合几项。包括数据项目的含义和字段表也非常类似,仅仅在访问标值和属性表集合有所区别。

img

img

​ ​ ​ ​ ​ ​ ​ 以访问标志来说,由于volatile和transient关键字无法修饰方法,所系方法表的访问标值没有ACC_VOLATILE和ACC_TRANSIENT,但是synchronized、native、strictfp、abstract关键字可以修饰方法,所以在方法标志中也相应添加了。方法的代码经过编译成字节码指令之后,放在了方法属性表中一个Code的属性里面。

img

​ ​ ​ ​ ​ ​ ​ 如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中的,但是想想Java中重载一个方法的时候,是无法仅仅依靠返回值不同来对一个已有方法重载的。

属性表集合
  • Code属性

    • 不是所有方法表都有这个属性,比如接口和抽象类中的方法就不存在。
  • Exceptions属性

    • 列举出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常
  • LineNumberTable属性

    • 描述Java源码行号与字节码行号之间的对应关系。
  • localVariableTable及LocalVariableTypeTable属性

    • LocalVariableTable属性用于描述栈帧中局部变量表的变量与Java源码中定义的变量之间的关系,它也不是运行时必需的属性,但默认会生成到Class文件之中,LocalVariableTypeTable属性是把记录的字段描述符的descriptor_index替换成了字段的特征签名(Signature)。
  • SourceFile及SourceDebugExtension属性

    • SourceFile属性用于记录生成这个Class文件的源码文件名称,可选属性。
  • ConstantValue属性

    • 通知虚拟机自动为静态变量赋值,只有被static关键字修饰的比Aoki昂才可以使用这个属性。
    • 类似“int x=123”和“static int x=123”这样的变量定义在Java程序里面是非常常见的事情,但虚拟机对这两种变量赋值的方式和时刻都有所不同。对非static类型的变量(也就是实例变量)的赋值是在实例构造器()方法中进行的;而对于类变量,则有两种方式可以选择:在类构造器()方法中或者使用ConstantValue属性。
  • InnerClasses属性

    • 用于记录内部类和宿主类之间的关系。
  • Deprecated及Synthetic属性

    • Deprecated属性用于表示某个类、字段或者方法,已经被程序作者定为不再推荐使用,它可以通过代码中使用“@deprecated”注解进行设置。
    • Synthetic属性代表此字段或者方法并不是由Java源码直接产生的,而是由编译器自行添加的。

    属性有很多,不一一介绍了…

字节码指令简介

​ ​ ​ ​ ​ ​ ​ Java虚拟机的指令由一个字节长度的、代表着某种特定操作含义的数字(也就是操作码)以及跟随其后的零到多个代表此操作需要的参数构成。大多数指令不包含操作数,只有一个操作码,指令参数都存放在操作数栈中。

字节码与数据类型

​ ​ ​ ​ ​ ​ ​ Java虚拟机的指令集中,大多数指令都包含其操作所对应的数据类型信息。对于大部分与数据类型相关的字节码指令,它们的操作码助记符中都有特殊的字符来表明专门为哪种数据类型服务:i代表对int类型的数据操作,l代表long,s代表short,b代表byte,c代表char,f代表float,d代表double,a代表reference。也有一些指令的助记符中没有明确指明操作类型的字母,例如arraylength指令,它没有代表数据类型的特殊字符,但操作数永远只能是一个数组类型的对象。还有另外一些指令,例如无条件跳转指令goto则是与数据类型无关的指令。

加载和存储指令

​ ​ ​ ​ ​ ​ ​ 用于将数据在栈帧中的局部变量表和操作数栈之间来回传输。

运算指令

​​ ​ ​ ​ ​ ​ ​ 用于对两个操作数栈上的值进行某种特定运算,并且把结果重新存入操作栈顶,大体上运算指令可以分为两种,对整形数据进行运算的指令和对浮点型数据进行运算的指令。

类型转换指令

​​ ​ ​ ​ ​ ​ ​ 可以将两种不同的数值类型相互转换,这些转换操作一般用于实现用户代码中的显式类型转换操作,或者用来处理字节码指令集中的数据类型相关指令无法与数据类型一一对应的问题。

对象创建与访问指令

​ ​ ​ ​ ​ ​ ​ 对象创建后,可以通过对象访问指令获取对象实例或者数组实例中的字段或者数组元素。

操作数栈管理指令

​ ​ ​ ​ ​ ​ ​ 列几个常见的指令:将操作数栈的栈顶一个或两个元素出栈:pop、pop2复制栈顶一个或两个数值并将复制值或双份的复制值重新压入栈顶:dup、dup2、dup_x1、dup2_x1、dup_x2、dup2_x2将栈最顶端的两个数值互换:swap

控制转移指令

​​ ​ ​ ​ ​ ​ ​ 让Java虚拟机有条件或无条件地从指定位置指令地下一条指令继续执行程序,从概念模型上理解,可以认为控制指令就是在有条件或无条件地修改PC寄存器的值。

方法调用和返回指令

​​ ​ ​ ​ ​ ​ ​ 方法调用指令和数据类型无关,而方法返回指令是根据返回值的类型区分的。这方面的问题在原书第八章进行了详细讲解,故本博也不多阐述了。

异常处理指令

​ ​ ​ ​ ​ ​ ​ 在Java虚拟机中,处理异常(catch语句)不是由字节码指令来实现的(很久之前曾经使用jsr和ret指令来实现,现在已经不用了),而是采用异常表来完成。

同步指令

​ ​ ​ ​ ​ ​ ​ 方法级的同步是隐式的,无须通过字节码指令来控制,它实现在方法调用和返回操作之中。同步一段指令集序列通常是由Java语言中的synchronized语句块来表示的,Java虚拟机的指令集中有monitorenter和monitorexit两条指令来支持synchronized关键字的语义,正确实现synchronized关键字需要Javac编译器与Java虚拟机两者共同协作支持。

参考博客:

《Java虚拟机原理图解》1.4 class文件中的字段表集合–field字段在class文件中是怎样组织的

《Java虚拟机原理图解》1.5、 class文件中的方法表集合–method方法在class文件中是怎样组织的

发布了58 篇原创文章 · 获赞 5 · 访问量 6257

猜你喜欢

转载自blog.csdn.net/weixin_40992982/article/details/104052638