JVM学习笔记——Class类文件解读

1,385 阅读14分钟

简述

Java源代码通过编译生成.class文件字节码后再被JVM解释转化为目标机器代码,从而实现一次编写到处,到处运行("Write Once,Run Anywhere")。字节码与平台无关,而且并不是只有Java语言编译为字节码文件在虚拟机上运行。

类文件的结构

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件中。Class文件只有两种数据类型:无符号数和表。

无符号数属于基本的数据类型,有u1, u2, u4, u8,分别代表1个字节、2个字节、4个字节和8个字节的无符号数

整个Class文件就是一张表,由以下数据项构成:

类型 名称 数量
u4 magic(魔数) 1
u2 minor_version(次版本号) 1
u2 major_version(主版本号) 1
u2 constant_pool_count(常量池容量) 1
cp_info constant_pool(常量池) constant_pool_count-1
u2 access_flags(访问标志) 1
u2 this_class(类索引) 1
u2 super_class(父类索引) 1
u2 interfaces_count(接口容量) 1
u2 interfaces(接口) interfaces_count
u2 fields_count(字段容量) 1
field_info fields(字段) fields_count
u2 methods_count(方法容量) 1
mehtod_info methods(方法) method_count
u2 attributes_count(属性容量) 1
attribute attributes(属性) attributes_count

小试牛刀

写个简单实体类,javac编译后,查看其字节码
源码


public class Person {

    private int age;

    public int getAge(){
        return age;
    }

    public static synchronized void work() {
        System.out.println("工作");
    }

    public static void main(String[] args) {

    }
}   

十六进制Class文件

根据上述数据项表格我们按顺序拆分

  • 魔数
  • 魔数站每个Class文件的头4个字节,其作用未确定确定这个文件是否为一个能被虚拟机接受的Class文件 示例中CA FE BA BE为魔数

  • 版本号
  • 魔数后面紧跟着版本号
    00 00——次版本号
    00 34——主版本号
    根据如下:

    十进制版本号 主版本
    jdk1.8 52
    jdk1.7 51
    jdk1.6 50
    jdk1.5 49
    jdk1.4 48
    jdk1.3 47
    jdk1.2 46
    jdk1.1 45
    十六进制0034,对应十进制52,对应jdk1.8版本

  • 常量池
  • 常量池可以理解为Class文件之中的资源仓库,它是Class文件结构中与其他项目关联最多的数据类型。常量池中主要存放两大类常量:字面量和符号引用.

    字面量——接近于Java中的常量概念,eg:final修饰、文本字符串
    符号引用——编译原理概念:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符

    常量池中的每一项常量都是一个表,每种常量都有自己的结构,14种常量含义:

    类型 标志 描述
    CONSTANT_utf8_info 1 utf-8编码的字符串
    CONSTANT_Integer_info 3 整型字面量
    CONSTANT_Float_info 4 浮点型字面量
    CONSTANT_Long_info 5 长整型字面量
    CONSTANT_Double_info 6 双精度浮点型字面量
    CONSTANT_Class_info 7 类或者接口的符号引用
    CONSTANT_String_info 8 字符串型字面量
    CONSTANT_Fieldref_info 9 字段的符号引用
    CONSTANT_Methodref_info 10 类中方法的符号引用
    CONSTANT_InterfaceMethoderf_info 11 接口中方法的符号引用
    CONSTANT_NameAndType_info 12 字段或方法的部分符号引用
    CONSTANT_MethodHandle_info 15 表示方法句柄
    CONSTANT_MethodType_info 16 标识方法类型
    CONSTANT_InvokeDynamic_info 18 表示一个动态方法调用点

    0×0029转十进制为41,代表常量池中有40项常量(容量计数是从1而不是0开始。第0项常量空出来是表达“不引用任何一个常量池项目”)

    0A即十进制10,对应表中CONSTANT_Methodref_info,其结构如下:

    类型 名称 描述
    u1 tag 值为10
    u2 index 指向声明方法的类描述符CONSTANT_Class_info的索引项
    u2 index 指向名称及类型描述符CONSTANT_NameAndType_info的索引项
    0×0007为常量池中第7项CONSTANT_Class_info,0×001A为第26项CONSTANT_NameAndType_info。按照《深入理解Java虚拟机》第二版,172页中表6-6顺序解析得:
    
    00 29                                         //constant_pool_count(常量池容量)
     #1、0A 0007 001A                             //CONSTANT_Methodref_info,#7,#26
     #2、09 0006 001B                             //CONSTANT_Fieldref_info,#6,#27
     #3、09 001C 001D                             //CONSTANT_Fieldref_info,#28,#29
     #4、08 001E                                  //CONSTANT_String_info,#30
     #5、0A 001F 0020                             //CONSTANT_Methodref_info,#31,#32
     #6、07 0021                                  //CONSTANT_Class_info,#33
     #7、07 0022                                  //CONSTANT_Class_info,#34
     #8、01 0003 61 67 65                         //CONSTANT_Utf8_info,3个字节,age
     #9、01 0001 49                               //CONSTANT_Utf8_info,1个字节,I
    #10、01 0006 3C 69 6E 69 74 3E                //CONSTANT_Utf8_info,6个字节,
    #11、01 0003 28 29 56                         //CONSTANT_Utf8_info,3个字节,()V
    #12、01 0004 43 6F 64 65                      //CONSTANT_Utf8_info,4个字节,Code
    #13、01 000F 4C 69 6E 65 4E 75 6D 62          //CONSTANT_Utf8_info,15个字节,LineNumberTable
                 65 72 54 61 62 6C 65    
    #14、01 0012 4C 6F 63 61 6C 56 61 72          //CONSTANT_Utf8_info,18个字节,LocalVariableTable
                 69 61 62 6C 65 54 61 62
                 6C 65
    #15、01 0004 74 68 69 73                       //CONSTANT_Utf8_info,4个字节,this
    #16、01 0008 4C 50 65 72 73 6F 6E 3B           //CONSTANT_Utf8_info,8个字节,LPerson;
    #17、01 0006 67 65 74 41 67 65                 //CONSTANT_Utf8_info,6个字节,getAge
    #18、01 0003 28 29 49                          //CONSTANT_Utf8_info,3个字节,()I
    #19、01 0004 77 6F 72 6B                       //CONSTANT_Utf8_info,4个字节,work
    #20、01 0004 6D 61 69 6E                       //CONSTANT_Utf8_info,4个字节,main
    #21、01 0016 28 5B 4C 6A 61 76 61 2F           //CONSTANT_Utf8_info,22个字节,([Ljava/lang/String;)V
                 6C 61 6E 67 2F 53 74 72
                 69 6E 67 3B 29 56
    #22、01 0004 61 72 67 73                       //CONSTANT_Utf8_info,4个字节,args
    #23、01 0013 5B 4C 6A 61 76 61 2F 6C           //CONSTANT_Utf8_info,19个字节,[Ljava/lang/String;
                 61 6E 67 2F 53 74 72 69
                 6E 67 3B
    #24、01 000A 53 6F 75 72 63 65 46 69           //CONSTANT_Utf8_info,10个字节, SourceFile
                 6C 65
    #25、01 000B 50 65 72 73 6F 6E 2E 6A           //CONSTANT_Utf8_info,11个字节, Person.java
                 61 76 61
    #26、0C 000A 000B                              //CONSTANT_NameAndType_info,#10,#11
    #27、0C 0008 0009                              //CONSTANT_NameAndType_info,#8,#9
    #28、07 0023                                   //CONSTANT_Class_info,#35
    #29、0C 0024 0025                              //CONSTANT_NameAndType_info,#36,#37
    #30、01 0006 E5 B7 A5 E4 BD 9C                 //CONSTANT_Utf8_info,6个字节,工作
    #31、07 0026                                   //CONSTANT_Class_info,#38
    #32、0C 0027 0028                              //CONSTANT_NameAndType_info,#39,#40
    #33、01 0006 50 65 72 73 6F 6E                 //CONSTANT_Utf8_info,6个字节,Person
    #34、01 0010 6A 61 76 61 2F 6C 61 6E           //CONSTANT_Utf8_info,16个字节,java/lang/Object
                 67 2F 4F 62 6A 65 63 74
    #35、01 0010 6A 61 76 61 2F 6C 61 6E           //CONSTANT_Utf8_info,16个字节, java/lang/System
                 67 2F 53 79 73 74 65 6D
    #36、01 0003 6F 75 74                          //CONSTANT_Utf8_info,3个字节,out
    #37、01 0015 4C 6A 61 76 61 2F 69 6F           //CONSTANT_Utf8_info,21个字节, Ljava/io/PrintStream;
                 2F 50 72 69 6E 74 53 74
                 72 65 61 6D 3B
    #38、01 0013 6A 61 76 2F 69 6F 2F 50           //CONSTANT_Utf8_info,19个字节,java/io/PrintStream
                 72 69 6E 74 53 74 72 65
                 61 6D
    #39、01 0007 70 72 69 6E 74 6C 6E              //CONSTANT_Utf8_info,7个字节,println
    #40、01 0015 28 4C 6A 61 76 61 2F 6C           //CONSTANT_Utf8_info,21个字节, (Ljava/lang/String;)V
                 61 6E 67 2F 53 74 72 69
                 6E 67 3B 29 56
    

    也可以java -verbose分析Class文件字节码,得到结果:

    
    Constant pool:
       #1 = Methodref          #7.#26         // java/lang/Object."":()V
       #2 = Fieldref           #6.#27         // Person.age:I
       #3 = Fieldref           #28.#29        // java/lang/System.out:Ljava/io/PrintStream;
       #4 = String             #30            // 工作
       #5 = Methodref          #31.#32        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #6 = Class              #33            // Person
       #7 = Class              #34            // java/lang/Object
       #8 = Utf8               age
       #9 = Utf8               I
      #10 = Utf8               
      #11 = Utf8               ()V
      #12 = Utf8               Code
      #13 = Utf8               LineNumberTable
      #14 = Utf8               LocalVariableTable
      #15 = Utf8               this
      #16 = Utf8               LPerson;
      #17 = Utf8               getAge
      #18 = Utf8               ()I
      #19 = Utf8               work
      #20 = Utf8               main
      #21 = Utf8               ([Ljava/lang/String;)V
      #22 = Utf8               args
      #23 = Utf8               [Ljava/lang/String;
      #24 = Utf8               SourceFile
      #25 = Utf8               Person.java
      #26 = NameAndType        #10:#11        // "":()V
      #27 = NameAndType        #8:#9          // age:I
      #28 = Class              #35            // java/lang/System
      #29 = NameAndType        #36:#37        // out:Ljava/io/PrintStream;
      #30 = Utf8               工作
      #31 = Class              #38            // java/io/PrintStream
      #32 = NameAndType        #39:#40        // println:(Ljava/lang/String;)V
      #33 = Utf8               Person
      #34 = Utf8               java/lang/Object
      #35 = Utf8               java/lang/System
      #36 = Utf8               out
      #37 = Utf8               Ljava/io/PrintStream;
      #38 = Utf8               java/io/PrintStream
      #39 = Utf8               println
      #40 = Utf8               (Ljava/lang/String;)V
    

  • 访问标志
  • 在常量池之后紧接着两个字节代表访问标志,用于识别一些类或者接口层次的访问信息
    具体的标志位和含义如下:

    名称 标志值 含义
    ACC_PUBLIC 0×0001 是否为public
    ACC_FINAL 0x0010 是否为final
    ACC_SUPER 0x0020 JDK 1.0.2之后编译出来的类这个标志都为真
    ACC_INTERFACE 0x0200 是否为一个接口
    ACC_ABSTRACT 0x0400 是否为abstract类型
    ACC_SUPER 0x0020 JDK 1.0.2之后编译出来的类这个标志都为真
    ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生
    ACC_ANNOTATION 0x2000 是否是注解
    ACC_ENUM 0x4000 是否是枚举

    没有使用到的标志位要求一律为0,本例access_flags的值为:ACC_PUBLIC | ACC_SUPER = 0x0021

  • 类索引、父类索引与接口索引
  • 类索引、父类索引与接口索引(指向常量池)都是u2类型的数据,除了java.lang.Object 之外所有的Java类都有父类,没有实现接口计数器为0,本例:

     
        0006         //this_class     Person
        0007         //super_class    java/lang/Object
        0000         //没有实现结构故0
    

  • 字段表集合
  • 字段表用于描述类和接口中声明的变量。字段包括类级变量和实例级变量,但是不包括方法中的变量。字段信息:字段的作用域,public/private/protected 实例变量还是类变量,static 可变性,final 并发可见性, volatile 可否被序列化, transient,字段数据类型(基本类型,对象,数组),字段名称

    字段表结构:

    类型 名称 数量
    u2 access_flags 1
    u2 name_index 1
    u2 descriptor_index 1
    u2 attributes_count 1
    attribute_info attributes attributes_count

    对于本例:
    
        0001             //fields_count 字段容量即1个字段
        0002             //访问标志 private
        0008             //常量池第8项,即age
        0009             //字段描述符,常量池第9项,即I
        0000             //attribute_count
    

  • 方法表集合
  • Class文件存储格式中对方法的描述与对字段的描述几乎采用完全一致的方式。
    方法表结构:

    类型 名称 数量
    u2 access_flags 1
    u2 name_index 1
    u2 descriptor_index 1
    u2 attributes_count 1
    attribute_info attributes attributes_count

    对于本例:
    
        0004         //方法容量,即4个方法:实例构造器、getAge()、work以及main方法
        0001         //方法访问标志,public
        000A         //常量池第10项,
        000B         //方法描述常量池第11个,()V,没返回值
        0001         //attribute_count
        000C         //常量池第12项,Code属性表,存放方法里的Java代码
        0000002F     //属性表长度  47
        ... 47个字节后
    
    0001         //方法访问标志,public
    0011         //常量池第17项,getAge
    0012         //方法描述常量池第18个,()I  返回int型
    0001         //attribute_count
    000C         //常量池第12项,Code属性表,存放方法里的Java代码
    0000002F     //属性表长度  47
    ... 47个字节后
    
    0029         // ACC_PUBLIC,ACC_STATIC,ACC_SYNCHRONIZED  0×0001|0×0008|0×0020
    0013         //常量池第19项,work
    000B         //方法描述常量池第11个,()V,没返回值
    0001         //attribute_count
    000C         //常量池第12项,Code属性表,存放方法里的Java代码
    00000025     //属性表长度  37
    ...37个字节后
    
    0009         //ACC_PUBLIC, ACC_STATIC  0×0001|0×0008
    0014         //常量池第20项,main
    0015         //方法描述常量池第21项,([Ljava/lang/String;)V  String数组形参,无返回类型方法
    0001         //attribute_count
    000C         //常量池第12项,Code属性表,存放方法里的Java代码
    0000002B     //属性表长度  43
    

  • 属性表集合
  • 对于本例:

    构造方法:

    
        000C                     //常量池第12项,Code属性
        0000002F                 //Code属性表长度  47
        0001                     //max_stack 操作数栈深度最大值  1
        0001                     //max_locals   局部变量存储
        00000005                 //code_length  字节码长度
        2A B7 00 01 B1           //字节码指令
        0000                     //exception_table_length
        0002                     //attributes_count  2个属性
        
        000D                     //常量池第13项, LineNumberTable属性
        00000006                 //LineNumberTable属性表长度
        0001                     //line_number_table_length
        0000                     //start_pc  字节码行号
        0001                     //line_number  Java源码行号
        
        000E                     //常量池第14项,LocalVariableTable属性
        0000000C                 //attribute_length 
        0001                     //local_variable_table_length
        0000                     //start_pc 这个局部变量的生命周期开始的字节码偏移量
        0005                     //局部变量作用范围覆盖的长度
        000F                     //name_index 局部变量名称 常量池第15项,this
        0010                     //descriptor_index 局部变量描述 常量池第16项,LPerson;
        0000                     //这个局部变量在栈帧局部变量表中Slot的位置
    

    getAge方法

    
        000C                     //常量池第12项,Code属性
        0000002F                 //attribute_length 
        0001                     //max_stack 操作数栈深度最大值  1
        0001                     //max_locals   局部变量存储
        00000005                 //code_length  字节码长度
        2A B4 00 02 AC           //字节码指令
        0000                     //exception_table_length
        0002                     //attributes_count  2个属性
        
        000D                     //常量池第13项, LineNumberTable属性
        00000006                 //LineNumberTable属性表长度
        0001                     //line_number_table_length
        0000                     //start_pc  字节码行号
        0006                     //line_number  Java源码行号
        
        000E                     //常量池第14项,LocalVariableTable属性
        0000000C                 //attribute_length
        0001                     //local_variable_table_length
        0000                     //start_pc 这个局部变量的生命周期开始的字节码偏移量
        0005                     //局部变量作用范围覆盖的长度
        000F                     //name_index 局部变量名称 常量池第15项,this
        0010                     //descriptor_index 局部变量描述 常量池第16项,LPerson;
        0000                     //这个局部变量在栈帧局部变量表中Slot的位置
    

    work方法

    
        000C                         //常量池第12项,Code属性
        00000025                     //attribute_length
        0002                         //max_stack 操作数栈深度最大值  2
        0000                         //max_locals   局部变量存储
        00000009                     //code_length  字节码长度
        B2 00 03 12 04 B6 00 05 B1   //字节码指令
        0000                         //exception_table_length
        0001                         //attributes_count  1个属性
        
        000D                         //常量池第13项, LineNumberTable属性
        0000000A                     //LineNumberTable属性表长度
        0002                         //line_number_table_length  2个line_number_info
        0000                         //start_pc  字节码行号
        000A                         //line_number  Java源码行号
        0008                         //start_pc  字节码行号
        000B                         //line_number  Java源码行号
    

    main方法

    
        000C                         //常量池第12项,Code属性
        0000002B                     //attribute_length
        0000                         //max_stack 操作数栈深度最大值  0
        0001                         //max_locals   局部变量存储
        00000001                     //code_length  字节码长度
        B1                           //字节码指令
        0000                         //exception_table_length
        0002                         //attributes_count  2个属性
        
        000D                         //常量池第13项, LineNumberTable属性
        00000006                     //LineNumberTable属性表长度
        0001                         //line_number_table_length  1个line_number_info
        0000                         //start_pc  字节码行号
        000F                         //line_number  Java源码行号
        
        000E                         //常量池第14项,LocalVariableTable属性
        0000000C                     //attribute_length
        0001                         //local_variable_table_length
        0000                         //start_pc 这个局部变量的生命周期开始的字节码偏移量
        0001                         //局部变量作用范围覆盖的长度
        0016                         //name_index 局部变量名称 常量池第22项,args
        0017                         //descriptor_index 局部变量描述 常量池第23项,[Ljava/lang/String;
        0000                         //这个局部变量在栈帧局部变量表中Slot的位置
    

    总结

    本篇做了一个小小的尝试,按照数据项表格一一解析,感兴趣的同学可以读下《深入理解Java虚拟机》这本圣书。

    感谢

    《深入理解Java虚拟机》