《深入理解JAVA虚拟机》笔记7——类文件结构

时间:2022-12-27 21:03:20

类文件结构

    在java语言发展之初,设计者们就曾经考虑过并实现了让其他语言运行在java虚拟机上的可能性,他们在发布规范文档的时候,也刻意把java的规范拆分成了java语言规范及java虚拟机规范。

java语言中的各种变量、关键字和去处符号的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力肯定会比java语言本身更强大。因此有一些java语言本身无法有效支持的语言特性并不代表字节码本身无法有效支持。

class类文件的结构

    class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部都是程序运行的必要数据 ,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前的方式分隔成若干个8位字节进行存储。
Class文件格采用一种类似于C语言结构体的伪结构来存储,这种伪结构中只有两种数据类型:无符号数和表,后面的解析都要以这两种数据类型为基础。
无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代码1个字节、2个字节、4个字节、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值,或者按照UTF-8编码构成字符串值。
表是由多个无符号数或其他表作为数据项构成的复合数据类型,所有表都习惯性的以_info结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表。
Class的结构不像XML等描述语言,由于它没有任何分隔符号,所以无论是顺序还是数量,都是被严格限定的,哪个字节代表什么含义,长度是多少,先后顺序 如何,都不允许改变。

魔数与Class文件的版本

    每个Class文件的头4个字节称为魔数,它的唯一作用是用于确定这个文件是否为一个虚拟机接受的Class文件。使用魔数而不是扩展名来进行识别主要是基于安全考虑,因为文件扩展名可以很随意地被改动。Class文件的魔数很有浪漫气息,值为0xCAFEBABE(咖啡宝贝?)。

紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次版本号,第7和第8个字节是主版本号。

常量池

    紧接着主次版本号之后的是常量池入口,常量池是Class文件结构中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一,同时它还是在Class文件中第一个出现的表类型数据项目。Class文件结构中只有常量池容量计数是从1开始的,对于其他集合类型,包括接口索引 集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。

常量池之中主要存放两大类常量:字面量和符号引用。字面量比较接近于java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用则属于编译原理方面的概念,包括了下面三类常量:
> 类和接口的全限定名
> 字段 的名称和描述符
> 方法的名称和描述符

当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析并翻译到具体的内存地址之中。

常量池中的每一项常量都是一个表,共有11种结构各不相同的表结构数据,这11种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位(tag,取值为1至12,缺少标志为2的数据类型),代表当前这个常量属于哪种常量类型。

Oracle公司已经为我们准备好一个专门用于分析Class文件字节码的工具:javap。

字段表集合

    java中描述一个字段可以包含什么信息?可以包含的信息有:字段的作用域(public、private、protected修饰符)、是类级变量还是实例级变量(static修饰符)、可变性(final)、并发可见性(volatile修饰符,是否强制从主内存读写)、可否序列化(transient修饰符)、字段数据类型(基本类型、对象、数组)、字段名称。

ACC_FINAL、ACC_VOLATILE不能同时选择。接口之中的字段必须有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志,这些都是由java本身的语言规则所决定的。


描述符的作用是用来描述字段 的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述规则 ,基本数据类型(byte,char,double,float,int,long,short,boolean)以及代表无返回值的void类型都是用一个大写字符来表示,而对象类型则是用字符L加对象的全限定名来表示。

对于数组类型,每一维度将使用一个前置的[字符来描述,如一个字义为java.lang.string[][]类型的二维数组,将被记录[[Ljava/lang/String;,一个整型数组int[]将被记录为[I。

用描述符来描述方法时,按照先参数列表,后返回值的顺序来描述。

方法表集合

    volatile关键字和transient关键字不能修饰方法,synchronized、native、strictfp和abstract关键字可以修饰方法。
方法里的java代码,经过编译器编译成字节码指令之后,存放在方法属性表集合中一个名为Code的属性里面,属性表作为Class文件格式中最具扩展性的一种数据项目。
java语言里是无法仅靠返回值的不同来对一个已有方法进行重载的。

属性表集合

    在Class文件、字段、方法表中都可以携带自己的属性表集合。

LocalVariableTable属性

    在javac中使用-g:none或-g:vars选项来取消或要求生成这项信息。如果没有生成这项属性,最大的影响就是当其他人引用这个方法时,所有参数的名称都将丢失,IDE可能会使用诸如arg0、arg1之类的占位符来代替原有的参数名。