类文件结构
文章目录
1 Class类文件结构
Class文件是一组以8位字节为基础单位的二进制流,当需要占用8位字节以上空间时,则会以高位在前的方式
分割成若干个8位字节进行存储。
Class文件格式采用类似于C语言结构体的伪结构进行存储,这种伪结构只包含两种数据类型:无符号数和表。
无符号数属于基本的数据类型。
表是由多个无符号数和其他表构成的复杂数据类型。
1.1 魔数与Class文件的版本
每个Class文件的头4个字节称为魔数(Magic Number),它的唯一作用是确定这个文件是否能被虚拟机接受。
Class文件的魔数值为:0xCAFEBABE(咖啡宝贝?)
紧接着魔数的4个字节是Class文件的版本号:第5第6是次版本号(Minor Version)。第7第8是主版本号
(Major Version),Java主版本号从45开始,JDK1.1之后每次版本大改加1,Java1.7是51.0,Java1.8是52.0.
高版本JDK能向下兼容以前的Class文件,但不能兼容以后的。
eg:
package test;
public class TestClass {
private int m;
public int inc() {
return m + 1;
}
}
编译之后,使用WinHex打开该Class文件,如图1.2,主版本号为0x0034,即十进制的52
1.3 常量池
常量池:可以理解为Class文件之中的资源仓库。常量池的入口是一个u2类型的数据,代表常量池容量计数值(只有
这个是从1计数,22代表常量池中有21项常量)。
常量池中主要有两类常量:字面量和符号引用。
字面量:与Java语言层面的意思类似,如文本字符串、声明为final的常量值等。
符号引用:1)类和接口的全限定名。2)字段的名称和描述符。3)方法的名称和描述符
常量池中每一个常量都是一个表,这些表有一个共同特点:开始的第一位是一个u1型的标志位,代表这个常量属于
什么类型。类型对应表如图1.3。
1.4 访问标志
在常量池之后,紧接着两个字节代表访问标志,这个标志用于识别一些类或接口的访问信息。
1.5 类索引、父类索引和索引接口集合
(1)类索引:u2类型数据,指向一个类型为CONSTANT_Class_info的类描述符,用于确定这个类的全限定名。
(2)父类索引:u2类型数据,指向一个类型为CONSTANT_Class_info的类描述符,用于确定这个类的父类的全限定名。
(3)接口索引集合:一组u2类型数据,入口第一项是接口计数器,后面则是接口索引。
1.6 字段表集合
字段表由于描述接口或类中声明的变量,包括类级变量、实例级变量,不包括局部变量。
注:字段表集合不会列出从超类或父接口继承的字段,但可能会列出原本Java中不存在的字段,比如:在内部类中
为了保持对外部类的访问性,会自动添加指向外部类实例的字段。
1.7 方法表的集合
Class文件存储格式对方法的描述与对字段的描述几乎采用同样的方式,仅在访问标志和属性集合表的可选项有所
区别。
1.8 属性表集合
1.8.1 Code 属性
Java程序方法体中的代码经过Javac编译器处理后,最终变成字节码指令存储在Code属性内。
1.8.2 Exception 属性
Exception属性的作用是列举出方法中可能抛出的受查异常,也就是方法描述时在throws关键字后面列举的异常。
1.8.3 LineNumberTable 属性
LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。
1.8.4 LocalVariableTable 属性
LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中的局部变量的对应关系。
1.8.5 SourceFile 属性
SourceFile属性用于记录生成这个Class文件的源文件的名称(一般类名与文件名相同,但有特殊,不如内部类)。
1.8.6 ConstantValue 属性
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。对于静态变量有两种赋值方式:在类构造器<clinit>
方法和使用ConstantValue属性。目前Sun javac编译期的选择是:当同时满足使用final、static修饰,并且是类型是
基本数据类型或java.lang.String,则生成ConstantValue属性进行初始化;否者在<clinit>中初始化。
1.8.7 InnerClasses 属性
InnerClasses属性用于记录内部类与宿主类之间的关联。
1.8.8 Deprecated 及 Synthetic 属性
Deprecated属性用于表示某个类、字段或方法已经被弃用,可以通过代码中添加@deprecated注释设置。
Synthetic属性表示该方法不是由Java源码直接产生的,而是又编译器自行合成的。
1.8.9 StackMapTable 属性
这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器使用,检查Class文件的合法性。
1.8.10 Signature 属性
任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量和参数化类型,则Signature属性会为它记录
泛型签名信息。
1.8.11 BootstrapMethods 属性
这个属性用于保存invokeDynamic指令引用的引导方法限定符。
2 字节码简介
由一个字节长度的、代表着某种操作含义的数字(操作码)以及跟着其后的所需参数(操作数)而构成。
2.1 字节码与数据类型
i:int; l:long; s:short; b:byte; c:char; f:float; d:double; a:reference;
2.2 加载和存储指令
- 将一个局部变量加载到操作栈:iload、iload_、lload、lload_、float、float_、dload、dload_、aload、aload_。
- 将一个数值存储到局部变量表:istore、istore_、lstore、lstore_、fstore、fstore_、dstore、dstore_、astore、astore_。
- 将一个常量加载到操作数栈:bipush、sipush、ldc、ldc_w、ldc2_w、aconst_null、iconst_null、iconst_ml、iconst_、iconst_、fconst_、dconst_。
- 扩充局部变量表的访问索引的指令:wide。
2.3 运算指令
- 加法指令:iadd、ladd、fadd、dadd。
- 减法指令:isub、lsub、fsub、dsub。
- 乘法指令:imul、lmul、fmul、dmul。
- 除法指令:idiv、ldiv、fdiv、ddiv。
- 求余指令:irem、lrem、frem、drem。
- 取反指定:ineg、lneg、fneg、dneg。
- 位移指令:ishl、ishr、iushr、lshl、lshr、lushr。
- 按位或指令:ior、lor。
- 按位与指令:iand、land。
- 按位异或指令:ixor、lxor。
- 局部变量自增指令:iinc。
- 比较指令:dcmpg、dcmpl、fcmpg、fcmpl、lcmp
2.4 类型转换指令
直接支持数值类型的宽化类型转换。
窄化类型转换指令:i2b、i2c、i2s、l2i、f2i、f2l、d2i、d2l和d2f。
2.5 对象创建与访问指令
- 创建类实例指令:new。
- 创建数组指令:newarray、anewarray、multianewarray。
- 访问类字段和实例字段:getfield、putfield、getstatic、putstatic。
- 把一个数组元素加载到操作数栈中:baload、caload、saload、iaload、laload、faload、daload、aaload。
- 将一个操作数栈中的值存储到数组中:bastore、castore、sastore、iastore、fastore、dastore、aastore。
- 取数组长度的指令:arraylength。
- 检查类实例类型的指令:instancaof、checkcast。
2.6 操作数栈管理指令
- 将操作数栈栈顶一个或两个元素出栈:pop、pop2。
- 复制栈顶一个或两个数值并将复制值或双份复制值重新压入栈顶:dup、dum2、dup_x1、dup2_x1、dup_x2、dup2_x2。
- 将栈顶两个数值进行交换:swap。
2.7 控制转移指令
- 条件分支:ifeq、iflt、ifle、ifne、ifgt、ifge、ifnull、ifnonnull、if_icmpeq、if_icmpne、if_icmplt、if_icmplt、if_icmpgt、if_icmple、if_icmpge、if_acmpeq、if_acmpne。
- 复合条件分支:tableswitch、lookupswitch。
- 无条件分支:goto、goto_w、jsr、jsr_w、ret。
2.8 方法调用与返回指令
- 方法调用指令:invokevirtual、invokeinterface、invokespecial、invokestatic、invokedynamic。
- 方法返回指令:ireturn、lreturn、freturn、dreturn、areturn。
2.9 异常处理指令
- athrow
2.10 同步指令
- monitorenter、monitorexit