Class文件
什么是class文件
在Java刚刚诞生的时候就提出了一个非常著名的口号:“一次编写,到处运行。(Write Once,Run Anywhere)”。为了实现平台无关性,各种不同平台的虚拟机都统一使用一种程序储存格式,就是字节码(ByteCode)。它就以二进制字节流的方式被存放在Class文件中,其中包含了Java虚拟机指令集和符号表以及其他辅助信息。
public class MyTest2 {
String str = "Welcome";
private int x = 5;
public static Integer in = 10;
public static void main(String[] args) {
MyTest2 myTest2 = new MyTest2();
myTest2.setX(8);
in = 20;
}
public void setX(int x) {
this.x = x;
}
}
Class是一组以8个字节为基础单位的二进制流,各个数据项目按照顺序紧凑地排列在文件之中。
魔数
0xCAFEBABE,用以标识这是一个class文件
支持的版本号
0x0033代表51,是jdk7所支持的版本
常量池
0x002F代表47,说明常量池中有46个项目,索引为1~46。常量中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量是比较接近Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用属于JVM编译原理层面的概念,主要包括:
- 类和接口的全限定名(org/fenixsoft/clazz/TestClass)
- 字段的名称和描述符(private static volatile int m;)
- 方法的名称和描述符(public synchronized operResource();)
- 方法句柄和方法类型
- 动态调用点和动态常量
常量池部分
offset为0x00000A的位置上的值为0A,代表其是一个CONSTANT_Methodref_info类型,每个CONSTANT类型都由一个公共的u1组合上其他部分构成,每个部分或代表长度,或指向常量池中不同的位置代表不同的值。
//方法引用类型
CONSTANT_Methodref_info {
u1 tag;
u2 class_index;
u2 name_and_type_index;
}
//String类型
CONSTANT_String_info {
u1 tag;
u2 string_index;
}
String的最大长度是多少?
String的底层实现是一个数组,其长度受限制于Integer的最大值,也就是说代码层面String的最大长度是21亿。在JVM中,String对应一个CONSTANT_Utf8_info。
//String类型
CONSTANT_Utf8_info {
u1 tag;
u2 string_index;
u1 bytes;
}
其中u2类型的String_index是两个字节存储的,其最大值为64KB,也就是说受限制于JVM层面,String的最大长度为65535
整个常量池的内容
Constant pool:
#1 = Methodref #10.#34 // java/lang/Object."<init>":()V
#2 = String #35 // Welcome
#3 = Fieldref #5.#36 // com/shengsiyuan/jvm/bytecode/MyTest2.str:Ljava/lang/String;
#4 = Fieldref #5.#37 // com/shengsiyuan/jvm/bytecode/MyTest2.x:I
#5 = Class #38 // com/shengsiyuan/jvm/bytecode/MyTest2
#6 = Methodref #5.#34 // com/shengsiyuan/jvm/bytecode/MyTest2."<init>":()V
#7 = Methodref #5.#39 // com/shengsiyuan/jvm/bytecode/MyTest2.setX:(I)V
#8 = Methodref #40.#41 // java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
#9 = Fieldref #5.#42 // com/shengsiyuan/jvm/bytecode/MyTest2.in:Ljava/lang/Integer;
#10 = Class #43 // java/lang/Object
#11 = Utf8 str
#12 = Utf8 Ljava/lang/String;
#13 = Utf8 x
#14 = Utf8 I
#15 = Utf8 in
#16 = Utf8 Ljava/lang/Integer;
#17 = Utf8 <init>
#18 = Utf8 ()V
#19 = Utf8 Code
#20 = Utf8 LineNumberTable
#21 = Utf8 LocalVariableTable
#22 = Utf8 this
#23 = Utf8 Lcom/shengsiyuan/jvm/bytecode/MyTest2;
#24 = Utf8 main
#25 = Utf8 ([Ljava/lang/String;)V
#26 = Utf8 args
#27 = Utf8 [Ljava/lang/String;
#28 = Utf8 myTest2
#29 = Utf8 setX
#30 = Utf8 (I)V
#31 = Utf8 <clinit>
#32 = Utf8 SourceFile
#33 = Utf8 MyTest2.java
#34 = NameAndType #17:#18 // "<init>":()V
#35 = Utf8 Welcome
#36 = NameAndType #11:#12 // str:Ljava/lang/String;
#37 = NameAndType #13:#14 // x:I
#38 = Utf8 com/shengsiyuan/jvm/bytecode/MyTest2
#39 = NameAndType #29:#30 // setX:(I)V
#40 = Class #44 // java/lang/Integer
#41 = NameAndType #45:#46 // valueOf:(I)Ljava/lang/Integer;
#42 = NameAndType #15:#16 // in:Ljava/lang/Integer;
#43 = Utf8 java/lang/Object
#44 = Utf8 java/lang/Integer
#45 = Utf8 valueOf
#46 = Utf8 (I)Ljava/lang/Integer;
访问标志部分
在常量池结束之后,紧跟的2个字节代表访问标志(access flag),用于表明类或者接口的访问信息。 此处为0x0021代表是ACC_PUBLIC和ACC_SUPER
ACC_SUPER 标志用于确定该 Class 文件里面的 invokespecial 指令使用的是哪一种执行语义。目前 Java 虚拟机的编译器都应当设置这个标志。ACC_SUPER 标记是为了向后兼容旧编译器编译的 Class 文件而存在的,在 JDK1.0.2 版本以前的编译器产生的 Class 文件中,access_flag 里面没有 ACC_SUPER 标志。同时,JDK1.0.2 前的 Java 虚拟机遇到 ACC_SUPER 标记会自动忽略它。
类索引、父类索引、接口索引集合
常量池中查找#5,该处的值为com/shengsiyuan/jvm/bytecode/MyTest2,即是类的索引,常量池中查找#9,该处的值为com/shengsiyuan/jvm/bytecode/MyTest2.in:Ljava/lang/Integer,为class文件中的父类索引。后面是 class文件中接口的索引。
字段表集合
03表示Fields[]有三个字段,00 00说明该字段没有修饰符,00 0b为该字段的名称'str',00 0c为该字段的类型。
#11 = Utf8 str
#12 = Utf8 Ljava/lang/String;
方法表集合
方法数量、修饰符(Synchronized)、方法名称等。
方法表中的项为methods_count、access_flag、name_index、descriptor_index、attributes_count、attributes_name_index
Code属性的16进制结构:0x0013=19,查询常量池索引19的位置得到属性的名字Code。然后是4个字节的attribute_length,0x00000042=66(jdk7和8可能有些不同),表示存储这个Code属性内容的字节数组的长度为66。所以往后再数66个字节结束,就得到方法完整的Code属性信息了,属性表紧跟在方法表的后面,依次为max_stack、max_locals(局部变量需要的存储空间)、code_length和真正的Code。