深入学习JVM之Class文件二进制解析

  |   0 评论   |   1,112 浏览

摘要

要深入学习JVM知识, 了解Class文件结构是必不可少的. 这个基础知识是后面了解JVM运行原理与内部细节不可或缺的前提条件. 否则学到的JVM知识都是趋于理论. 难以融会贯通, 也难对以后的JAVA程序调优有实用性的指导. 最终使理论知识和实际工程脱节: 理论一套一套, 实践却好像是另外一回事.因此有必要对Class文件进行字节层面的剖析. 以加深JVM在编译层面与运行层面的理解.

目前,大部分的分析CLASS文件的文章,大部分把焦点聚焦在了常量池上面.而对于class文件的其它细节和整体概貌却有缺失. 这篇文章主要是把一个最简单的Class文件的所有字节都讲清楚. 这样可以对Class文件的整体结构有更清晰的认识.

class文件格式概述.

参考<深入理解Java虚拟机: jvm高级特性与最佳实践>的定义:

Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地 排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎 全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项 时,则会按照高位在前的方式分割成若干个8位字节进行存储。
根据Java虚拟机规范的规定,Class文件格式采用一种类似于C语言结构体的伪结构来存 储数据,这种伪结构中只有两种数据类型:无符号数和表,后面的解析都要以这两种数据类 型为基础,所以这里要先介绍这两个概念

我们参考第7版本的JVM虚拟机规范中,对于Class文件的定义. 这里把一个class文件定义为了一个C语言伪结构题, 这个很重要.后面所有的解析都要以这个结构体为准.

Oracle JVM规范第7版 - Class文件说明
所有JVM相关的最权威的最直接的知识都来自于此, 如果对于JVM有任何的疑问,其实都可以先到此来找答案.

CLASS结构定义

一个Class文件的大体结构是:

  • 文件标识: u4 magic , 取值固定为:0xCAFEBABE
  • 小版本号: minor_version
  • 大版本号:major_version
  • 常量池常量数: constant_pool_count
  • 常量池 (个数为 count-1): cp_info : constant_pool[constant_pool_count-1]
  • 访问标识: access_flags
  • 当前类: this_class
  • 父类: super_class
  • 接口数量: interfaces_count
  • 接口数组: interface[interfaces_count]
  • 字段数量: fields_count
  • 字段数组: field_info fields[fields_count];
  • 方法数量: methods_count
  • 方法数组: method_info methods[methods_count];
  • 属性数量: attributes_count
  • 属性数组: attribute_info attributes[attributes_count];

定义链接:

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

Tips: 后面仔细看过Class文件的内容后你会发现. Class文件可以大体分为: 常量(池) + 代码.
所有代码/字段相关的被封装在一个个的结构体里面. 而这些结构体里面需要的一些的附加的描述, 则全部引用常量池里面的常量. 比如类全限定名,父类名称,接口名称,方法名称等.

这里引用了一个国外的教授对CLASS文件的基本解析的PPT , 如果要全面深入的了解CLASS文件的结构可以先看看. 如果想直接实战, 则可以先略过. 后面需要时再回过来看.

源文件:

package train.jvm;

/**
 * @author lijianhong Date: 2022/1/24 Time: 1:51 AM
 * @version $Id$
 */
public class TestClass {

    private Integer age;

    private String name;

    public String sayHello() {
        return "hi,My Name is " + name;
    }
}

常量类型type定义:

public class CONSTANT_TYPE {
    public static final int CONSTANT_Utf8 = 1;                     // u1 (tag) , u2 (length) , u1 (bytes) 
    public static final int CONSTANT_Integer = 3;
    public static final int CONSTANT_Float = 4;
    public static final int CONSTANT_Long = 5;
    public static final int CONSTANT_Double = 6;
    public static final int CONSTANT_Class = 7;
    public static final int CONSTANT_String = 8;
    public static final int CONSTANT_Fieldref = 9;
    public static final int CONSTANT_Methodref = 10;
    public static final int CONSTANT_InterfaceMethodref = 11;
    public static final int CONSTANT_NameAndType = 12;              // u1(tag) , u2 (index - 指向该字段或方法 名称常量项 的索引) , u2 (index - 指向该字段或者方法 描述符常量的索引)
    public static final int CONSTANT_MethodHandle = 15;
    public static final int CONSTANT_MethodType = 16;
    public static final int CONSTANT_InvokeDynamic = 18;
    private ConstantPool.CPInfo[] pool;
}

属性信息类型定义

ClassFile {
    u4             magic;
    u2             minor_version;
    u2             major_version;
    u2             constant_pool_count;
    cp_info        constant_pool[constant_pool_count-1];
    u2             access_flags;
    u2             this_class;
    u2             super_class;
    u2             interfaces_count;
    u2             interfaces[interfaces_count];
    u2             fields_count;
    field_info     fields[fields_count];
    u2             methods_count;
    method_info    methods[methods_count];
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

attribute_info {
    u2 attribute_name_index;
    u4 attribute_length;
    u1 info[attribute_length];
}

method_info {
    u2             access_flags;
    u2             name_index;
    u2             descriptor_index;
    u2             attributes_count;
    attribute_info attributes[attributes_count];
}

Code_attribute {
    u2 attribute_name_index;
    u4 attribute_length;
    u2 max_stack;
    u2 max_locals;
    u4 code_length;
    u1 code[code_length];
    u2 exception_table_length;
    {   u2 start_pc;
        u2 end_pc;
        u2 handler_pc;
        u2 catch_type;
    } exception_table[exception_table_length];
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}
CAFE BABE -- 标识: 0xCafeBabe
0000 0034 -- 版本: 小版本:0,大版本:52

# ---------------------------------------------------- 常量池 ----------------------------------------------------------

0021      -- 常量个数: 2*16+1-1 = 32个

          #1
0A        -- CONSTANT_Methodref_info
00 09     -- 指向声明方法的 类描述符 ( CONSTANT_Class_info  ) 的 索引
00 16     -- 指向 名称及类型描述符   ( CONSTANT_NameAndType ) 的 索引

          #2
07        -- 类描述符号 ( CONSTANT_Class_info ) 
0017      -- 指向全限定名常量项的索引 #23                                // java/lang/StringBuilder

          #3
0A        -- CONSTANT_Methodref_info
00 02     -- 指向声明方法的 类描述符 ( CONSTANT_Class_info  ) 的 索引
00 16     -- 指向 名称及类型描述符   ( CONSTANT_NameAndType ) 的 索引

          #4
08        -- CONSTANT_String_info
0018 

          #5
0A        -- CONSTANT_Methodref_info   
00 02     -- 指向声明方法的 类描述符 ( CONSTANT_Class_info  ) 的 索引
00 19     -- 指向 名称及类型描述符   ( CONSTANT_NameAndType ) 的 索引


          #6
09 
0008 
001A 

          #7
0A        -- CONSTANT_Methodref_info  
00 02     -- 指向声明方法的 类描述符 ( CONSTANT_Class_info  ) 的 索引  
00 1B     -- 指向 名称及类型描述符   ( CONSTANT_NameAndType ) 的 索引  

          #8
07        -- 类描述符号 ( CONSTANT_Class_info )    
001C      -- 指向全限定名常量项的索引 #28                 // train/jvm/TestClass 
 
          #9   
07        -- 类描述符号 ( CONSTANT_Class_info )    
00 1D     -- 指向全限定名常量项的索引 #29                 // java/lang/Object

          #10
01        -- CONSTANT_Utf8_info
0003      -- length: 3
6167 65   -- String: "age"


          #11
01        -- CONSTANT_Utf8_info
0013      -- length: 19 / 
4C6A 6176 612F 6C61 6E67 2F49 6E74 6567 6572 3B   -- String: "Ljava/lang/Integer;"

          #12
01        -- CONSTANT_Utf8_info
0004      -- length: 4
6E61 6D65 -- String: "name"

          #13
01        -- CONSTANT_Utf8_info
00 12     -- length: 18 
4C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 3B     -- String: "Ljava/lang/String;"

          #14
01        -- CONSTANT_Utf8_info
0006      -- length: 6
3C69 6E69 743E  -- String: "<init>" 

          #15
01        -- CONSTANT_Utf8_info
00 03     -- length: 3
28 2956   -- String: "()V"

          #16
01        -- CONSTANT_Utf8_info
00 04     -- length: 4
43 6F64 65  -- String: "Code"


          #17
01        -- CONSTANT_Utf8_info
000F      -- length: 15
4C69 6E65 4E75 6D62 6572 5461 626C 65              : "LineNumberTable"

          #18
01        -- CONSTANT_Utf8_info
0008      -- length: 8
7361 7948 656C 6C6F       : "sayHello"

          #19
01        -- CONSTANT_Utf8_info
00 14     -- length: 20
28 294C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 3B : "()Ljava/lang/String;"


          #20
01        -- CONSTANT_Utf8_info
000A      -- length: 10
536F 7572 6365 4669 6C65                           : "SourceFile"

          #21
01        -- CONSTANT_Utf8_info
00 0E     -- length: 14
54 6573 7443 6C61 7373 2E6A 6176 61                : "TestClass.java"

          #22
0C        -- CONSTANT_NameAndType_info            
000E      -- 名称常量项    #14 "<init>" 
000F      -- 描述符常量项   #15 "()V"                 => // "<init>":()V

          #23
01        -- CONSTANT_Utf8_info
00 17     -- length: 23
6A 6176 612F 6C61 6E67 2F53 7472 696E 6742 7569 6C64 6572  : "java/lang/StringBuilder"


          #24
01
00 0E     -- length: 14
68 692C 4D79 204E 616D 6520 6973 20                 : "hi,My Name is"


          #25
0C        -- CONSTANT_NameAndType_info
001E      -- 名称常量项:  #30
001F      -- 描述符常量项 #31                             // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;

          #26
0C        -- CONSTANT_NameAndType_info
00 0C     -- 名称常量项:  #12                            // name
00 0D     -- 描述符常量项  #13                           // Ljava/lang/String;

          #27
0C        -- CONSTANT_NameAndType_info
0020      -- 名称常量项:   #32  
0013      -- 描述符常量项  #19

          #28
01        -- CONSTANT_Utf8_info
00 13     -- length: 19
74 7261 696E 2F6A 766D 2F54 6573 7443 6C61 7373             : train/jvm/TestClass


          #29
01        -- CONSTANT_Utf8_info
00 10     -- length: 16
6A 6176 612F 6C61 6E67 2F4F 626A 6563 74                    : java/lang/Object


          #30
01        -- CONSTANT_Utf8_info
0006      -- length: 6
6170 7065 6E64 


          #31
01        -- CONSTANT_Utf8_info
00 2D     -- length: 45                                       : (Ljava/lang/String;)Ljava/lang/StringBuilder;
28 4C6A 6176 612F 6C61 6E67 2F53 7472 696E 673B 294C 6A61 7661 2F6C 616E 672F 5374 7269 6E67 4275 696C 6465 723B     


          #32
01        -- CONSTANT_Utf8_info
00 08     -- length: 8
74 6F53 7472 696E 67                                          : toString


# --------------------------------------- 访问标识 ----------------------------------------------------------------------

          # access flag
00 21     -- 0000 0000 0010 0001
                         ^     ^
                         |     |____ ACCESS_PUBLIC
                         |__________ ACCESS_SUPER             : 是否使用invoke special字节码的新语意. 1.0.2之后编译的类这个标识需要为真   
                       
# ---------------------------------------- 类索引 -----------------------------------------------------------------------                       
        
          # this_class                      
00 08     -- #8/CONSTANT_Class_info -> #28/CONSTANT_Utf8_info : "train/jvm/TestClass"

# ---------------------------------------- 父类索引 ---------------------------------------------------------------------                       
          # super_class   
00 09     -- #9/CONSTANT_Class_info -> #29/CONSTANT_Utf8_info  : "java/lang/Object"


# ---------------------------------------- 接口索引 ---------------------------------------------------------------------                       
          # interface_counts
00 00     -- 有零个接口

----------------------------------------- 字段表集合 --------------------------------------------------------------------
> 用于描述接口或者类中声明的变量. 字段包含: 类级变量 / 实例级变量.

          # field_counts
00 02     -- 有两个字段  

          # private "Ljava/lang/Integer;" : "age"
00 02     -- access_flags 0x02:       ACC_PRIVATE/字段是否为private
00 0A     -- name_index: #10      --> CONSTANT_Utf8_info: "age"
00 0B     -- descriptor_index:#11 --> CONSTANT_Utf8_info: "Ljava/lang/Integer;"
00 00     -- attributes_count:    --> 0

          # 
00 02     -- access_flags 0x02:       ACC_PRIVATE/字段是否为private
00 0C     -- name_index: #12      --> CONSTANT_Utf8_info: "name"
00 0D     -- descriptor_index:#13 --> CONSTANT_Utf8_info: "Ljava/lang/String;"
00 00     -- attributes_count:    --> 0

----------------------------------------- 方法表集合 --------------------------------------------------------------------
00 02     -- 有两个字段  

          // 方法信息
          method_info {
              u2             access_flags;
              u2             name_index;
              u2             descriptor_index;
              u2             attributes_count;
              attribute_info attributes[attributes_count];
          }
        
          // 属性信息
          attribute_info {
              u2 attribute_name_index;
              u4 attribute_length;
              u1 info[attribute_length];
          }

          # method_info
00 01     -- access_flags 0x01:       ACC_PUBLIC/字段是否为public
00 0E     -- name_index: #14                --> CONSTANT_Utf8_info: "<init>"
00 0F     -- descriptor_index:#15           --> CONSTANT_Utf8_info: "()V"
00 01     -- attributes_count:1

          # attribute_info
          Code_attribute {
              u2 attribute_name_index;
              u4 attribute_length;
              u2 max_stack;
              u2 max_locals;
              u4 code_length;
              u1 code[code_length];
              u2 exception_table_length;
              {   u2 start_pc;
                  u2 end_pc;
                  u2 handler_pc;
                  u2 catch_type;
              } exception_table[exception_table_length];
              u2 attributes_count;
              attribute_info attributes[attributes_count];
          }
00 10     -- attribute_name_index: #16      --> CONSTANT_Utf8_info: "code"
00 0000 1D-- attribute_length               --> 属性长度: 29

00 01     -- max_stack:   栈深度为1
00 01     -- max_locals:  最大局部变量
00 0000 05-- code_length: 5
          -- ---------> 代码 <---------
2A        // aload_0 , 从局部变量表加载一个 reference 类型值到操作数栈中
B700      // 0xb7 invokespecial 调用超类构造方法,实例初始化方法,私有方法。
          // 请注意,所有使用 invokespecial 指令调用的方法都需要 this 作为第一个参数,保存在 第一个局部变量之中。

01        // aconst_null , 将 null 推送至栈顶。      
B1        // return 从当前方法返回 void。

0000      -- exception_table_length
0001      -- attributes_count:  1
        
          // LineNumberTable 属性是可选变长属性,位于 Code(§4.7.3)结构的属性表。
          // 它被调试 器用于确定源文件中行号表示的内容在 Java 虚拟机的 code[]数组中对应的部分。
          // 在 Code 属性 的属性表中,LineNumberTable 属性可以按照任意顺序出现,此外,\
          // 多个 LineNumberTable 属性可以共同表示一个行号在源文件中表示的内容,
          // 即 LineNumberTable 属性不需要与源文件 的行一一对应。
          LineNumberTable_attribute {
              u2 attribute_name_index;
              u4 attribute_length;
              u2 line_number_table_length;
              {   u2 start_pc;
                  u2 line_number;
              } line_number_table[line_number_table_length];
          }
0011      -- #17 --> UTF8:      LineNumberTable
0000 0006 -- attribute_length:  6
0001 0000 0007 

00 01     -- access_flags 0x01:                 ACC_PUBLIC/字段是否为public
00 12     -- name_index: #18                --> CONSTANT_Utf8_info: "sayHello"
00 13     -- descriptor_index:#19           --> CONSTANT_Utf8_info: "()Ljava/lang/String;" 
00 01     -- attributes_account:1
00 10     -- attribute_name_index: #16      --> CONSTANT_Utf8_info: "code"
0000 002F -- attribute_length               --> 属性长度: 47
          -- ---------> 代码 <---------
0002      -- max_stack:   栈深度为 2
0001      -- max_locals:  最大局部变量 1
0000 0017 -- code_length: 23 
BB        // new , 创建一个对象,并将其引用值压入栈顶。
00 
02        // iconst_m1      , 将 int 型-1 推送至栈顶
59        // dup            , 复制栈顶数值并将复制值压入栈顶
B7        // invokespecial  , 调用超类构造方法,实例初始化方法,私有方法。
00 
03        // 0x03 iconst_0  , 将 int 型 0 推送至栈顶。
12 04     // 0x12 ldc       , 将 int,float 或 String 型常量值从常量池中推送至栈顶 , 这里是把 #4号推送到栈顶. (#4: hi,My Name is)
 
B6 0005   // 0xb6 invokevirtual  , 调用实例方法。  invoke #5 (实际为append方法): java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder; 
2A        // aload_0             , 从局部变量表加载一个 reference 类型值到操作数栈中 ; 将第一个引用类型局部变量推送至栈顶
B4 00     // 0xb4 getfield       , 获取指定类的实例域,并将其值压入栈顶。
06 B600 05B6 0007 
B0        // areturn 从当前方法返回对象引用。

00 00     -- exception_table_length: 0 
00 01     -- attributes_count:       1 代码属性中的内部属性个数,1
00 11     -- #17 --> UTF8:      LineNumberTable 
00 0000 06-- attribute_length:  6
00 0100 0000 0E

----------------------------------------- 属性表集合 --------------------------------------------------------------------
00 01     -- 属性表 信息 Count: 1
          attribute_info {
              u2 attribute_name_index;
              u4 attribute_length;
              u1 info[attribute_length];
          }
        
00 14     -- attribute_name_index: #20 -> "SourceFile" 
00 0000 02-- attribute_length
          SourceFile_attribute {
              u2 attribute_name_index;
              u4 attribute_length;
              u2 sourcefile_index;
          }
00 15     -- sourcefile_index: #21 --> "TestClass.java"

javap 编译结果

Classfile /Users/lijianhong/IdeaProject/tec_note/train/src/main/java/train/jvm/TestClass.class
  Last modified Jan 24, 2022; size 521 bytes
  MD5 checksum 71f6dbdf29861390494d8c565872f1dd
  Compiled from "TestClass.java"
public class train.jvm.TestClass
  minor version: 0
  major version: 52
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #9.#22         // java/lang/Object."<init>":()V
   #2 = Class              #23            // java/lang/StringBuilder
   #3 = Methodref          #2.#22         // java/lang/StringBuilder."<init>":()V
   #4 = String             #24            // hi,My Name is
   #5 = Methodref          #2.#25         // java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
   #6 = Fieldref           #8.#26         // train/jvm/TestClass.name:Ljava/lang/String;
   #7 = Methodref          #2.#27         // java/lang/StringBuilder.toString:()Ljava/lang/String;
   #8 = Class              #28            // train/jvm/TestClass
   #9 = Class              #29            // java/lang/Object
  #10 = Utf8               age
  #11 = Utf8               Ljava/lang/Integer;
  #12 = Utf8               name
  #13 = Utf8               Ljava/lang/String;
  #14 = Utf8               <init>
  #15 = Utf8               ()V
  #16 = Utf8               Code
  #17 = Utf8               LineNumberTable
  #18 = Utf8               sayHello
  #19 = Utf8               ()Ljava/lang/String;
  #20 = Utf8               SourceFile
  #21 = Utf8               TestClass.java
  #22 = NameAndType        #14:#15        // "<init>":()V
  #23 = Utf8               java/lang/StringBuilder
  #24 = Utf8               hi,My Name is
  #25 = NameAndType        #30:#31        // append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
  #26 = NameAndType        #12:#13        // name:Ljava/lang/String;
  #27 = NameAndType        #32:#19        // toString:()Ljava/lang/String;
  #28 = Utf8               train/jvm/TestClass
  #29 = Utf8               java/lang/Object
  #30 = Utf8               append
  #31 = Utf8               (Ljava/lang/String;)Ljava/lang/StringBuilder;
  #32 = Utf8               toString
{
  public train.jvm.TestClass();
    descriptor: ()V
    flags: ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 7: 0

  public java.lang.String sayHello();
    descriptor: ()Ljava/lang/String;
    flags: ACC_PUBLIC
    Code:
      stack=2, locals=1, args_size=1
         0: new           #2                  // class java/lang/StringBuilder
         3: dup
         4: invokespecial #3                  // Method java/lang/StringBuilder."<init>":()V
         7: ldc           #4                  // String hi,My Name is
         9: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        12: aload_0
        13: getfield      #6                  // Field name:Ljava/lang/String;
        16: invokevirtual #5                  // Method java/lang/StringBuilder.append:(Ljava/lang/String;)Ljava/lang/StringBuilder;
        19: invokevirtual #7                  // Method java/lang/StringBuilder.toString:()Ljava/lang/String;
        22: areturn
      LineNumberTable:
        line 14: 0
}
SourceFile: "TestClass.java"

评论

发表评论


取消