阅读深入理解java虚拟机(六)

184 阅读5分钟

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;
    }
}

image.png

Class是一组以8个字节为基础单位的二进制流,各个数据项目按照顺序紧凑地排列在文件之中。

魔数

0xCAFEBABE,用以标识这是一个class文件

支持的版本号

0x0033代表51,是jdk7所支持的版本

常量池

0x002F代表47,说明常量池中有46个项目,索引为1~46。常量中主要存放两大类常量:字面量(Literal)和符号引用(Symbolic Reference)。字面量是比较接近Java语言层面的常量概念,如文本字符串、被声明为final的常量值等。而符号引用属于JVM编译原理层面的概念,主要包括:

  1. 类和接口的全限定名(org/fenixsoft/clazz/TestClass)
  2. 字段的名称和描述符(private static volatile int m;)
  3. 方法的名称和描述符(public synchronized operResource();)
  4. 方法句柄和方法类型
  5. 动态调用点和动态常量

常量池部分

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;

访问标志部分

image.png

在常量池结束之后,紧跟的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 标记会自动忽略它。

类索引、父类索引、接口索引集合

image.png

常量池中查找#5,该处的值为com/shengsiyuan/jvm/bytecode/MyTest2,即是类的索引,常量池中查找#9,该处的值为com/shengsiyuan/jvm/bytecode/MyTest2.in:Ljava/lang/Integer,为class文件中的父类索引。后面是 class文件中接口的索引。

字段表集合

image.png image.png

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

image.png

Code属性的16进制结构:0x0013=19,查询常量池索引19的位置得到属性的名字Code。然后是4个字节的attribute_length,0x00000042=66(jdk7和8可能有些不同),表示存储这个Code属性内容的字节数组的长度为66。所以往后再数66个字节结束,就得到方法完整的Code属性信息了,属性表紧跟在方法表的后面,依次为max_stack、max_locals(局部变量需要的存储空间)、code_length和真正的Code。 image.png