【Java虚拟机】class类文件结构
平台无关性
Java的无关性的实现,是由Java源代码编译后生成的字节码class文件和Java虚拟机实现的。无关性包括:平台无关性以及语言无关性。
(1) 平台无关性,是指java代码可以运行在不同操作系统的虚拟机上面,因为不同的操作系统都有各自的Java虚拟机,从而实现程序的“一次编写,到处运行”。
(2) 语言无关性,是指Java虚拟机能运行除Java以外的代码,JVM执行的是class文件,也就是JVM只认识class文件,它并不关心Class文件的来源是什么语言,只要它符合一定的结构,就可以在Java中运行。
类文件结构
class文件是Java虚拟机执行引擎的数据入口,是一组以8位字节为基础单位的二进制流,存储的内容几乎都是程序运行的必要数据。
class文件的数据存储结构中只有两种数据类型:无符号数和表。class文件中的数据项,都被严格限定的,每个字节代表的含义,长度,先后顺序等都不允许改变。
(1) 无符号数属于基本数据类型,以u1、u2、u4、u8来分别代表1、2、4、8个字节的无符号数。
(2) 表是由多个无符号数或其他表作为数据项构成的复合数据类型。
class类文件的结构:
1. 魔数
2. 文件版本信息
3. 常量池
4. 访问标志
5. 类索引、父类索引、接口索引集合
6. 字段表集合
7. 方法表集合
8. 属性表集合
魔数
每个Class文件的头4个字节称为魔数(magic),它的唯一作用是判断该文件是否为一个能被虚拟机接受的Class文件。它的值固定为0xCAFEBABE。
文件版本信息
紧接着magic的4个字节存储的是Class文件的次版本号和主版本号,高版本的JDK能向下兼容低版本的Class文件,但不能运行更高版本的Class文件,即使该版本没有用到高版本JDK的特性。
常量池
紧接着版本号之后的就是常量池入口,它是class文件中的资源仓库。它是Class文件中与其他项目关联最多的数据类型,也是占用Class文件空间最大的数据项目之一。常量池中主要存放两大类常量:字面量和符号引用。
字面量比较接近于Java层面的常量概念,如文本字符串、被声明为final的常量值等。
而符号引用总结起来则包括了下面三类常量:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
访问标志
在常量池结束之后,紧接着的2个字节代表访问标志(access_flag),这个标志用于识别一些类或接口层次的访问信息,包括:这个Class是类还是接口,是否定义为public类型,abstract类型,如果是类的话,是否声明为final等。
类索引、父类索引、接口索引集合
Class文件中由这三项数据来确定这个类的继承关系。类索引、父类索引和接口索引集合都按照顺序排列在访问标志之后。类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名。接口索引集合就用来描述这个类实现了哪些接口。
字段表集合
字段表(field_info)用于描述接口或类中声明的变量。字段包括了类级变量或实例级变量,但不包括在方法内声明的变量。可以包括的信息有:字段的作用域、是实例变量还是类变量、可变性、并发可见性、可否被序列化、字段数据类型、字段名称。但是,字段的名字、数据类型、修饰符等都是无法固定的,只能引用常量池中的常量来描述。
字段表集合中不会列出从父类或接口中继承而来的字段,但有可能列出原本Java代码中不存在的字段。比如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
方法表集合
方法表(method_info)的结构与字段表的结构相同。方法里的Java代码,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里。
与字段表集合相对应,如果父类方法在子类中没有被覆写,方法表集合中就不会出现来自父类的方法信息。但同样,有可能会出现由编译器自动添加的方法,最典型的便是类构造器“<clinit>”方法和实例构造器“<init>”方法。
重载一个方法需要有相同的简单名称和不同的特征签名。JVM的特征签名和Java的特征签名有所不同:
- Java特征签名:方法参数在常量池中的字段符号引用的集合
- JVM特征签名:方法参数+返回值
属性表集合
属性表(attribute_info),在Class文件、字段表、方法表中都可以携带自己的属性表集合,以用于描述某些场景专有的信息。
Code属性
Java程序方法体中的代码经过Javac编译后,生成的字节码指令便会存储在Code属性中,但并非所有的方法表都必须存在这个属性,比如接口或抽象类中的方法就不存在Code属性。
Code属性是Class文件中最重要的一个属性,如果把一个Java程序中的信息分为代码和元数据两部分,那么在整个Class文件里,Code属性用于描述代码,所有的其他数据项目都用于描述元数据。
Exception属性
这里的Exception属性的作用是列举出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。
LineNumberTable属性
它用于描述Java源码行号与字节码行号之间的对应关系。
LocalVariableTable属性
它用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的对应关系。
SourceFile属性
它用于记录生成这个Class文件的源码文件名称。
ConstantValue属性
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值,只有被static修饰的变量才可以使用这项属性。
在Java中,对非static类型的变量(也就是实例变量)的赋值是在实例构造器<init>方法中进行的;而对于类变量(static变量),则有两种方式可以选择:在类构造其中赋值,或使用ConstantValue属性赋值。
如果同时使用final和static修饰一个变量(即全局常量),并且这个变量的数据类型是基本类型或String的话,就生成ConstantValue属性来进行初始化,如果该变量没有被final修饰,或者并非基本类型及字符串,则选择在<clinit>方法中进行初始化。
InnerClasses属性
该属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那么编译器将会为它及它所包含的内部类生成InnerClasses属性。
Deprecated属性
Deprecated该属性用于表示某个类、字段和方法,已经被程序作者定为不再推荐使用,它可以通过在代码中使用@Deprecated注释进行设置。
Synthetic属性
该属性代表此字段或方法并不是Java源代码直接生成的,而是由编译器自行添加的,如this字段和实例构造器、类构造器等。
参考: |