类文件结构
无关性的基石
实现语言无关性的基础是虚拟机和字节码存储格式。
Java虚拟机只和“Class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。
Java语言中各种变量、关键字和运算符号的语义最终都是由多条字节码命令组合而成的,因此字节码命令所能提供的语义描述能力比Java语言本身更强大。
Class类文件结构
注:任何一个Class文件都对应着唯一一个类或接口的定义信息,但反过来说,类或接口并不一定都得定义在文件里(譬如类或接口库也可以通过类加载器直接生成)。
Class文件是一组以8位字节为基础单位的二进制流,用大端方式存储(最高位字节在地址最低位)。
Class文件采用类似C语言结构体的伪结构来存储数据,伪结构有两种数据类型:无符号数和表。
- 无符号数:基本的数据类型,用u1,u2,u4,u8代表1,2,4,8个字节的无符号数,无符号数可以用来描述数字,索引引用、数量值或者按照UTF-8编码构成字符串值。
- 表:由多个无符号数或者其他表作为数据项构成的复合数据类型,一般以“_info”结尾,Class文件本质上就是一张表。
Class没有任何分隔符号,所以它的格式,顺序,数量,数据存储的字节序都是严格限定的,哪个字节代表什么,长度多少,顺序如何都是不可变化的。
魔数与Class文件的版本
Class文件的头4个字节称为魔数,唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。
(之所以不用扩展名是因为文件扩展名可以随意地改动)
Class文件的魔数值为:0xCAFEBABE
第5,6个字节是次版本号(Minor Version),第7,8个字节是主版本号(Major Version)。
Java的版本号从45开始,JDK1.1后每个JDK大版本发布主版本号向上加1
常量池
主版本号之后的是常量池,常量池可以理解为Class文件中的资源仓库。
常量池的入口处放置一项u2类型的数据,代表常量池容量计数值,容量计数从1开始。(索引值为0用于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义)。
主要存放:
字面量: 文本字符串、声明为final的常量值等
符号引用:
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址中。
常量池中每一项常量都是一个表,每个表开始第1位是一个u1类型标志位,代表当前这个常量属于哪种常量类型。
javap工具 -versbos阐述可以输出class文件字节码内容。
eg:javap -verbose TestClass
访问标志
在常量池结束后,两个字节代表访问标志,用于识别一些类或者接口层次的访问信息。包括Class是类还是接口,是否定义为public类型,是否定义为abstract类型,如果是类是否声明为final等。
类索引、父类索引与接口索引集合
类索引用于确定这个类的全限定名。
父类索引用于确定这个类的父类的全限定名。
接口索引集合用于描述这个类实现了哪些接口,这些接口按照implements语句后的接口顺序从左到右排列在接口索引集合中。
字段表集合
字段表用于描述接口或者类中声明的变量。
字段的作用域(public,private,protected修饰符),是实例变量还是类变量(static修饰符),可变性(final),并发可见性(volatile修饰符,是否强制从主内存读写),可否被序列化(transient)等
方法表集合
对方法的描述与对字段的描述几乎采用了完全一致的方式。
方法里的Java带啊吗,经过编译器编译成字节码指令后,存放在方法属性表集合中一个名为“Code”的属性里面。
属性表集合
在Class文件、字段表、方法表都可以携带自己的属性表集合,用于描述某些场景专有信息。
- Code属性
用于存放Java程序方法体中的代码经过Javac编译器处理后变成的字节码指令。 - Exception属性
列举出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。 - LineNumberTable属性
描述Java源码行号与字节码行号之间的对应关系。
…
(感觉这里不是很重要,没有记了)