简介
在前文中我们已经简单说了说class类文件的结构,但是由于我是根据JVM规范进行快速学习解释所以部分的并不是很清楚,这篇文章是在我阅读了多本数有关类结构的部分后总结出的文章或者可以叫做读书笔记。
Class类文件结构
总所周知,我们的JVM在运行时不会与任何语言绑定而是只与"Class"文件进行绑定。Class文件是一组以字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在文件之中,没有任何的分隔符,所以Class结构是被严格规范的,无论是顺序还是数量还是数据存储的字节序。
Class文件的内容
Class文件格式采用一种类结构体的数据结构来存储数据,该结构存在两种数据类型:"无符号数" ,"表"。
- 无符号数:为基本数据类型,以u1,u2,u4,u8 来分别代表1个字节,2个字节,4个字节,8个字节的无符号数,这个无符号数可以用来描述数字,索引引用,数量值等等。
- 表:是由多个无符号数或者其他代表作文数据项构成的复合数据类型,为便于区分使用"_info"作为命名的结尾。
然后我拿来了第一篇文章中的表结构示意代码:
ClassFile {
u4 magic;
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文件的魔数为 0xCAFEBABEc
- 然后紧跟着的4个字节是次版本号和主版本号
- 然后是常量池入口,常量池是Class文件结构中与其他项目关联最多的数据,通常也是占用Class文件空间最大的数据项目之一。由于常量池中常量的数量是不一定的,所以前两个字节用于表示常量池中的常量数量,后续的两个字节是表类型数据项目。常量的数目索引值从1开始因为常量池索引值的数据在特定情况下需要表达出**"不引用仍以常量池项目"含义,** 此时就可以把索引值设置为0来表示(注:在Class文件结构中只有常量池里面常量数目的值索引是从1开始的,其他都是从0开始 )。
- 常量池存储对象:
- 字面量:比较接近于Java语言层面的常量概念,如文本字符串、被声明为fianl的常量值等。
- 符号引用则属于编译原理方面的概念,主要包含下面几类常量:
- 被模块带出或者开放的包
- 类和接口全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 方法句柄和方法类型
- 动态调用点和动态常量
- 注意Java在使用Javac命令进行编译时,并没有"连接"这一步骤,而是虚拟机加载Class文件的时候进行动态连接。
- 常量池中每一项常量都是一个表,截至JDK13存在17种不同类型的表,表结构起始的第一位是一个u1类型的标志位(tag) 用于代表当前常量属于哪种常量类型。17种常量有各自不同的,完全独立的数据结构,两两之间没有共性和联系
- 常量池存储对象:
- 常量池结束后就是2个字节代表访问标志,用于记录识别一些类或接口层次的访问信息。access_flags 中一共有16个标志位可以用,当前只定义了其中9个,没有使用到的一律定义为零。
- 紧跟着访问标志后的是两个u2类型的 类索引 和父类索引,由于Java不允许多重继承,所以父类索引只有一个,同样的除了Object以外,所有的Java类都有父索引即父索引不为0.
- 类索引后就是接口相关的数据结构两个u2数据结构,代表了接口数量和接口集合按在implement关键字后的顺序从左到右排列在接口索引集合中。
- 然后就是一个字段表,用于描述接口或者类中声明的变量。字段表结构包括了修饰符有字段的作用域,是实例变量还是类变量,可变性,并发可见性,可否被序列化,字段数据类型,字段名称。上述各个类型都是boolean值,需要注意的是:
- ACC_PUBLIC 和 ACC_PRIVATE, ACC_PROTECTED三选一
- ACC_FINAL, ACC_VOLATILE不能同时选择
- ACC_PUBLIC, ACC_STATIC, ACC_FINAL在接口中必须声明
- 注:对简单名和全限定名做一个简单的介绍:1. 比如 org/fenixsoft/clazz/TestClass 把类全名的 " . " 替换成 " / " 即可。需注意每个类全面后面需要加一个 " ; " 2.简单名称则是值没有类型和参数修饰的方法或者字段名称。
- 对于其中对象的表示,基础类型以及 void 都用一个大写字母表示,对象类型前面必须加一个L,对于数组类型,每一维度将使用一个前置的 " [ " 字符来描述 如:"[Ljava/lang/String"