2、无关性的基石
3、Class类文件的结构
- 任意一个有效的类或接口所应当满足的格式称为“Class文件格式”,实际上它
并不一定以磁盘文件的形式存在 - Class文件是一组以8位字节为基础单位的二进制流;当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前[1]的方式分割成若干个8位字节进行存储
- Class文件格式,只有两种数据类型:无符号数和表;
- 无符号数属于基本的数据类型,以u1、u2、u4、u8来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串;
- 表是由多个
无符号数
或者其他表
作为数据项构成的复合数据类型 - 表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表
- 无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,经常会使用一个前置的
容量计数器
加若干个连续的数据项
的形式,这时称这一系列连续的某一类型的数据为某一类型的集合
【Class文件格式】
本章示例代码
package study6;
/**
* Created by haicheng.lhc on 30/03/2017.
*
* @author haicheng.lhc
* @date 2017/03/30
*/
public class TestClass {
private int m;
public int inc() {
return m + 1;
}
}
3.1 魔数与Class文件的版本
- 每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否为一个能被虚拟机接受的Class文件。
- 紧接着魔数的4个字节存储的是Class文件的版本号:第5和第6个字节是次号(Minor Version),第7和第8个字节是主版本号(Major Version)。
3.2 常量池
- 常量池可以理解为Class文件之中的资源仓库
- 它是Class文件结构中与其他项目关联最多的数据类型
- 是占用Class文件空间最大的数据项目之一
- 还是在Class文件中第一个出现的表类型数据项目
- 常量池的入口需要放置一项u2类型的数据,代表常量池容量计数值(constant_pool_count)。
- 常量池中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic References)
-
字面量比较接近于Java语言层面的常量概念,如
文本字符串
、声明为final的常量值
等 - 符号引用则属于编译原理方面的概念,包括了下面三类常量:
1、类和接口的全限定名(Fully Qualified Name)
2、字段的名称和描述符(Descriptor)
3、方法的名称和描述符
PS:Java代码在进行Javac编译的时候没有连接这一步,是在虚拟机加载Class文件的时候进行动态连接。Class文件中不会保存
各个方法、字段的最终内存布局信息,因此这些字段、方法的符号引用不经过运行期转换的话无法得到真正的内存入口地址,也就无法直接被虚拟机使用。当虚拟机运行时,需要从常量池获得对应的符号引用,再在类创建时或运行时解析、翻译到具体的内存地址之中
** 常量池中每一项常量都是一个表**
每一项的表结构
本章示例代码对应的常量表
~/vmstudy/src/main/java » javap -v study6.TestClass
Compiled from "TestClass.java"
public class study6.TestClass extends java.lang.Object
SourceFile: "TestClass.java"
minor version: 0
major version: 50
Constant pool:
const #1 = Method #4.#15; // java/lang/Object."<init>":()V
const #2 = Field #3.#16; // study6/TestClass.m:I
const #3 = class #17; // study6/TestClass
const #4 = class #18; // java/lang/Object
const #5 = Asciz m;
const #6 = Asciz I;
const #7 = Asciz <init>;
const #8 = Asciz ()V;
const #9 = Asciz Code;
const #10 = Asciz LineNumberTable;
const #11 = Asciz inc;
const #12 = Asciz ()I;
const #13 = Asciz SourceFile;
const #14 = Asciz TestClass.java;
const #15 = NameAndType #7:#8;// "<init>":()V
const #16 = NameAndType #5:#6;// m:I
const #17 = Asciz study6/TestClass;
const #18 = Asciz java/lang/Object;
3.3 访问标志
在常量池结束之后,紧接着的两个字节代表访问标志(access_flags),这个标志用于识别一些类或者接口层次的访问信息,包括:这个Class是类还是接口
;是否定义为public类型
;是否定义为abstract类型
;如果是类的话,是否被声明为final等
。
3.4 类索引、父类索引与接口索引集合
- 类索引(this_class)是一个u2类型的数据,用于确定这个类的全限定名
- 父类索引(super_class)是一个u2类型的数据,父类索引用于确定这个类的父类的全限定名
- 接口索引集合(interfaces)是一组u2类型的数据的集合
- Class文件中由这三项数据来确定这个类的继承关系
3.5 字段表集合
- 字段表(field_info)用于描述接口或者类中声明的变量
- 字段(field)包括
类级变量
以及实例级变量
,但不包括在方法内部声明的局部变量
access_flags可取值如下表
name_index是对常量池的引用,代表着字段的简单名称
descriptor_index是对常量池的引用,代表字段的描述符
“简单名称”、“描述符”以及前面出现过多次的“全限定名”这三种特殊字符串的概念。
全限定名,“org/fenixsoft/clazz/TestClass”是这个类的全限定名,仅仅是把类全名中的“.”替换成了“/”而已,为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个“;”表示全限定名结束
简单名称是指没有类型和参数修饰的方法或者字段名称,inc()方法和m字段的简单名称分别是“inc”和“m”。
描述符的作用是用来描述字段的数据类型
、方法的参数列表(包括数量、类型以及顺序)和返回值
。
对于数组类型,每一维度将使用一个前置的“[”字符来描述,如一个定义
为“java.lang.String[][]”类型的二维数组,将被记录为:“[[Ljava/lang/String;”,一个整型数组“int[]”将被记录为“[I”
用描述符来描述方法时,按照先参数列表,后返回值的顺序描述,参数列表按照参数的严格顺序放在一组小括号“()”之内
void inc()的描述符为 "()V",
java.lang.String toString() 的描述符为 "()Ljava/lang/String;",
int indexOf(char[]source, int sourceOffset, int sourceCount,char[] target, int targetOffset, int targetCount, int fromIndex)的描述符为 "([CII[CIII)I"
3.6 方法表集合
- 方法表的结构如同字段表一样,依次包括了访问标志(access_flags)、名称索引(name_index)、描述符索引(descriptor_index)、属性表集合(attributes)几项
3.7 属性表集合
- 属性表(attribute_info)在前面的讲解之中已经出现过数次,在Class文件、字段表、方法表都可以携带自己的属性表集合,
以用于描述某些场景专有的信息
。
- 虚拟机规范定义的属性
- Code属性Java程序方法体中的代码经过Javac编译器处理后,最终变为字节码指令存储在Code属性内。Code属性出现在方法表的属性集合之中,但并非所有的方法表都必须存在这个属性,譬如接口或者抽象类中的方法就不存在Code属性