1.Class类文件结构
Class文件是一组以8个字节为基础单位的二进制字节流,各个数据项目严格按照顺序紧凑地排列在文件之中,中间没有添加任何分隔符,Class文件格式采用一种类似于C语言结构体的伪结构来存储数据
这种伪结构中只有两种数据类型:“无符号数”与“表”
- 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节、8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值
- 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,为了方便区分,所有的表命名都习惯的以“_info”结尾,表用于描述有层次关系的复合结构的数据
1.1 魔数与class文件的版本
每个Class文件的头4个字节被称之为魔数,它的唯一作用是确定这个文件是否为一个能被虚拟机接收的Class文件,魔数的值为“0xCAFEBABE”,紧接着魔数的4个字节存储的是Class的版本号,第5、6个字节存储的是次版本号(Minor Version),第7、8个字节存储的是主版本号(Major Version)
1.2 常量池
紧接着主、次版本号之后就是常量池入口,常量池可以比喻为Class文件里的资源仓库,它是Class文件结构中与其他项目关联最多的数据,也是在Class文件中出现的第一个表类型数据项目
常量池中主要存放两大类常量:
- 字面量(字面量比较接近与Java语言层面的常量概念,如文本字符串以及被声明final的常量值等)
- 符号引用(而符号引用则属于编译原理方面的概念,主要包括以下几类常量:
- 被模块导出或者开放的包
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
- 方法句柄和方法类型
- 动态调用点和动态常量
Java代码在进行Javac编译的时候,不像是C或者C++那样有“连接”这一步骤,而是在虚拟机加载Class文件的时候进行动态连接,也就是说,在Class文件中不会保存各个方法、字段最终在内存中的布局信息,这些字段、方法的符号引用不经过虚拟机在运行期转换的话无法的到真正的内存入口地址,也就是无法直接被虚拟机使用的,当虚拟机做类加载的时候,将会在常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中
1.3 访问标志
在常量池结束之后,紧接着2个字节代表访问标志,这个标志用于识别一些类或者接口层次的访问信息,具体的标志图如下:
标志名称 | 标志值 | 含义 |
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语义,JDK1.0.2时候编译出来的类这个标志都为真 |
ACC_INTERFACE | 0x0200 | 标识这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型 |
ACC_SYNTHETIC | 0x1000 | 标识这个类并非用户代码生成 |
ACC_ANNOTATION | 0x2000 | 标识这是一个注解 |
ACC_ENUM | 0x4000 | 标识这是一个枚举 |
ACC_MODULE | 0x8000 | 标识这是一个模块 |
1.4 类索引、父类索引与接口索引集合
类索引和父类索引都是一个u2类型的数据,而接口索引集合是一组u2类型的数据的集合,Class文件中由这三项数据来确定该类型的继承关系
类索引用于确定这个类的全限定名,父类索引用于确定这个类的父类的全限定名(由于Java不允许多重继承,所以父类索引只有一个,除了Object之外,所有的java类都有父类,所有的父类索引都不为0)
1.5 字段表集合
字段表用于描述接口或者类中声明的变量,Java语言中的“字段”包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量,一个字段可以包含哪些信息呢?
- 有字段的作用域(public、private、protected修饰符)
- 实例变量、类变量(static修饰)
- 可变性(final)
- 并发可见性(volatile修饰符)
- 可否被序列化(transient修饰符)
- 字段数据类型(基本类型、对象、数组)、字段名称