之前已经学习了有关JVM的组成、内存泄漏原因、垃圾回收机制和内存的动态分配、以及常用的Java工具和JVM实战案例;现在开始学习Java类文件结构
一、Class类文件结构
1. 无关平台
Java语言的一大特性就是无关平台,其实现根本是在Java进行编译时,并非直接编译成能够被计算机读懂的本地二进制机器码,而是先转换成与操作系统、机器指令集无关的、平台中立的字节码文件进行存储;然后通过对字节码文件解释执行,来运行程序。
2. Class文件结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑的排列在Class文件中,中间无任何分隔符;当遇到需要占用8位字节以上空间的数据项时,按照高位在前的方式分割成若干个8位字节进行存储。
Class文件结构采用类似于C语言结构体的伪结构来存储,该伪结构中只有两种数据类型:无符号数和表
1)无符号数
属于基本的数据类型,以u1、u2、u4、u8表示各种字节的无符号数,可用于描述数字、索引引用、数量值、按照UTF-8编码构成的字符串值。
2)表
由多个无符号数或其他表作为数据项构成的复合数据类型
3. 魔数
魔数是为了判定文件是否是一个能被虚拟机接受的Class文件,出于安全考虑,保证不会被任意修改,此处不采取扩展名而是采取这种方式。
每个Class文件的头四个字节为CAFEBABE(这里我们使用十六进制编译器打开任一Class文件),第五第六字节为次版本号,第七第八字节为主版本号;这就是Class文件的魔数。
4. 常量池
紧接着主次版本号的是常量池入口,由于常量池中常量的数量并不固定,需要在常量池入口放置一项u2类型的数据,代表常量池容量计数值。
常量池之中主要存放两大类常量。
1)字面量
其比较接近Java语言层面常量的概念,如文本字符串、被声明为final的常量值
2)符号引用
## 类和接口的全限定名
## 字段的名称和描述符
## 方法的名称和描述符
5、访问标志
在常量池结束之后,紧接着两个字节代表访问标志(access_flags),这个标志用于识别一些类或接口层次的访问信息,包括:这个Class是类还是接口;是否定义为public类型;是否定义为abstract类型;如果是类的话,是否被声明为final等等
6. 类索引、父类索引与接口索引集合
类和父类索引是一个u2类型数据,而接口索引集合是一组u2类型数据的集合,Class文件中由这三项数据来确定这个类的继承关系。
1)类索引
用于确定这个类的全限定名
2)父类索引
用于确定这个类的父类的全限定名(只有java.lang.Object父类索引为0)
3)接口索引
用来描述这个类实现了那些接口,那些被实现接口将按照implements语句后的接口顺序从左向右排列在接口的索引集合中
对于接口索引集合,入口的第一项——u2类型的数据为接口计数器,表示索引表的容量,如果该类没有实现任何接口,那么该计数器值为0
## 根据值去常量池中找出对应的类、父类、接口的常量值
7. 字段表集合
字段表(field_info)用于描述接口或类中声明的变量。
字段包含了类级变量或实例级变量,但不包含方法内部声明的变量
字段表包含信息:
1)字段作用域(public protected private)
2)实例变量还是类变量(static)
3)可变性(final)
4)并发可见性(volatile,是否强制从主内存读写)
5)可否序列化(transient)
6)字段的数据类型
7)字段名称
其中,修饰符都是布尔值用标志位来表示;字段名和数据类型用常量池中的常量来表示
8. 方法表集合
Class文件存储格式中堆方法的描述与对字段的描述几乎采用了完全统一的方式,结构也与字段表一样,依次包括了访问标志(access_flags,即修饰符)、名称索引(name_index,即字段名称)、描述符索引(descriptor_index, 即子弹的数据类型)、属性表集合(attributes, 一些额外信息)
要知道这些只能表示方法的基本信息,而方法中的Java代码的位置呢?
其实方法里的Java代码存放在方法属性表集合中一个名为“Code”的属性中
9. 属性表集合
对于每个属性,名称用常量池中一个CONSTANT_Utf8_info类型的常量来表示,属性的结构可以自定义,只需要说明属性值所占用的位数长度
1)Code属性
Java程序方法体内代码经过Javac编译器处理后,变为字节码指令存储在Code属性中