美文网首页
1.Java字节码文件分析

1.Java字节码文件分析

作者: 风吹尘埃 | 来源:发表于2023-03-08 17:05 被阅读0次

    Java字节码文件内容概览

    1. 魔数: 文件类型
    2. 主次版本号: jdk版本
    3. 常量池: Class文件的资源仓库
      3.1. 常量池容量计数
      3.2. 常量,主要是字面量和符号引用两类
    4. 访问标志: 声明是否接口, public还是private等
    5. 类索引: 类的全限定名
    6. 父类索引: 父类的全限定名
    7. 接口数量和接口索引: 接口数量和接口的全限定名, 如果接口数为0则接口索引不存在
    8. 字段数量和字段集合
    9. 方法数和方法集合
    10. Attributes

    各种相关表格

    常量池数据类型表

    • 其中u1,u2,u4,u8等代表占用1,2,4,8字节


      pool1.png
      pool2.png
      pool3.png

    类访问标志

    Access_Flags.png

    字段

    • 字段表结构


      field_table.png
    • 字段表访问标志


      field_access_flags.png

    方法

    • 方法表结构


      method_table.png
    • 方法表访问标志


      method_access_flags.png

    属性表

    • Code属性表
      • attribute_name_index: 固定执行 Code
      • max_stack代表了操作数栈(Operand Stack)深度的最大值。
      • max_locals代表了局部变量表所需的存储空间。
      • code_length和code用来存储Java源程序编译后生成的字节码指令。
      • exception_length和exception用来处理trycatch异常
      • attribute: 属性
    code_table.png

    字节码文件分析

    • 生成对应文件命令(Win PowerShell下)
    # 需要先将java文件编译成class文件
    # class字节码输出到class.txt中
    javap -verbose .\HelloWord.class > class.txt
    # class十六进制输出到hex.txt中
    format-hex -path .\HelloWord.class > hex.txt
    

    源码文件

    • HelloWord.java
    public class HelloWord {
    
        private int c = 300;
        public int d = 400;
    
        public static void main(String[] args) {
            int a = 100;
            int b = 200;
    
            System.out.println("hello word");
        }
    }
    
    • HelloWord.class
    public class com.example.demo.HelloWord
      minor version: 0
      major version: 52
      flags: (0x0021) ACC_PUBLIC, ACC_SUPER
      this_class: #7                          // com/example/demo/HelloWord
      super_class: #8                         // java/lang/Object
      interfaces: 0, fields: 2, methods: 2, attributes: 1
    Constant pool:
       #1 = Methodref          #8.#28         // java/lang/Object."<init>":()V
       #2 = Fieldref           #7.#29         // com/example/demo/HelloWord.c:I
       #3 = Fieldref           #7.#30         // com/example/demo/HelloWord.d:I
       #4 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;
       #5 = String             #33            // hello word
       #6 = Methodref          #34.#35        // java/io/PrintStream.println:(Ljava/lang/String;)V
       #7 = Class              #36            // com/example/demo/HelloWord
       #8 = Class              #37            // java/lang/Object
       #9 = Utf8               c
      #10 = Utf8               I
      #11 = Utf8               d
      #12 = Utf8               <init>
      #13 = Utf8               ()V
      #14 = Utf8               Code
      #15 = Utf8               LineNumberTable
      #16 = Utf8               LocalVariableTable
      #17 = Utf8               this
      #18 = Utf8               Lcom/example/demo/HelloWord;
      #19 = Utf8               main
      #20 = Utf8               ([Ljava/lang/String;)V
      #21 = Utf8               args
      #22 = Utf8               [Ljava/lang/String;
      #23 = Utf8               a
      #24 = Utf8               b
      #25 = Utf8               MethodParameters
      #26 = Utf8               SourceFile
      #27 = Utf8               HelloWord.java
      #28 = NameAndType        #12:#13        // "<init>":()V
      #29 = NameAndType        #9:#10         // c:I
      #30 = NameAndType        #11:#10        // d:I
      #31 = Class              #38            // java/lang/System
      #32 = NameAndType        #39:#40        // out:Ljava/io/PrintStream;
      #33 = Utf8               hello word
      #34 = Class              #41            // java/io/PrintStream
      #35 = NameAndType        #42:#43        // println:(Ljava/lang/String;)V
      #36 = Utf8               com/example/demo/HelloWord
      #37 = Utf8               java/lang/Object
      #38 = Utf8               java/lang/System
      #39 = Utf8               out
      #40 = Utf8               Ljava/io/PrintStream;
      #41 = Utf8               java/io/PrintStream
      #42 = Utf8               println
      #43 = Utf8               (Ljava/lang/String;)V
    {
      public int d;
        descriptor: I
        flags: (0x0001) ACC_PUBLIC
    
      public com.example.demo.HelloWord();
        descriptor: ()V
        flags: (0x0001) ACC_PUBLIC
        Code:
          stack=2, locals=1, args_size=1
             0: aload_0
             1: invokespecial #1                  // Method java/lang/Object."<init>":()V
             4: aload_0
             5: sipush        300
             8: putfield      #2                  // Field c:I
            11: aload_0
            12: sipush        400
            15: putfield      #3                  // Field d:I
            18: return
          LineNumberTable:
            line 3: 0
            line 5: 4
            line 6: 11
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      19     0  this   Lcom/example/demo/HelloWord;
    
      public static void main(java.lang.String[]);
        descriptor: ([Ljava/lang/String;)V
        flags: (0x0009) ACC_PUBLIC, ACC_STATIC
        Code:
          stack=2, locals=3, args_size=1
             0: bipush        100
             2: istore_1
             3: sipush        200
             6: istore_2
             7: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
            10: ldc           #5                  // String hello word
            12: invokevirtual #6                  // Method java/io/PrintStream.println:(Ljava/lang/String;)V
            15: return
          LineNumberTable:
            line 9: 0
            line 10: 3
            line 12: 7
            line 13: 15
          LocalVariableTable:
            Start  Length  Slot  Name   Signature
                0      16     0  args   [Ljava/lang/String;
                3      13     1     a   I
                7       9     2     b   I
        MethodParameters:
          Name                           Flags
          args
    }
    SourceFile: "HelloWord.java"
    
    • HelloWord十六进制
    CA FE BA BE 00 00 00 34 00 2C 0A 00 08 00 1C 09
    00 07 00 1D 09 00 07 00 1E 09 00 1F 00 20 08 00
    21 0A 00 22 00 23 07 00 24 07 00 25 01 00 01 63
    01 00 01 49 01 00 01 64 01 00 06 3C 69 6E 69 74
    3E 01 00 03 28 29 56 01 00 04 43 6F 64 65 01 00
    0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
    01 00 12 4C 6F 63 61 6C 56 61 72 69 61 62 6C 65
    54 61 62 6C 65 01 00 04 74 68 69 73 01 00 1C 4C
    63 6F 6D 2F 65 78 61 6D 70 6C 65 2F 64 65 6D 6F
    2F 48 65 6C 6C 6F 57 6F 72 64 3B 01 00 04 6D 61
    69 6E 01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E
    67 2F 53 74 72 69 6E 67 3B 29 56 01 00 04 61 72
    67 73 01 00 13 5B 4C 6A 61 76 61 2F 6C 61 6E 67
    2F 53 74 72 69 6E 67 3B 01 00 01 61 01 00 01 62
    01 00 10 4D 65 74 68 6F 64 50 61 72 61 6D 65 74
    65 72 73 01 00 0A 53 6F 75 72 63 65 46 69 6C 65
    01 00 0E 48 65 6C 6C 6F 57 6F 72 64 2E 6A 61 76
    61 0C 00 0C 00 0D 0C 00 09 00 0A 0C 00 0B 00 0A
    07 00 26 0C 00 27 00 28 01 00 0A 68 65 6C 6C 6F
    20 77 6F 72 64 07 00 29 0C 00 2A 00 2B 01 00 1A
    63 6F 6D 2F 65 78 61 6D 70 6C 65 2F 64 65 6D 6F
    2F 48 65 6C 6C 6F 57 6F 72 64 01 00 10 6A 61 76
    61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 01 00 10
    6A 61 76 61 2F 6C 61 6E 67 2F 53 79 73 74 65 6D
    01 00 03 6F 75 74 01 00 15 4C 6A 61 76 61 2F 69
    6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B 01 00
    13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74
    72 65 61 6D 01 00 07 70 72 69 6E 74 6C 6E 01 00
    15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72
    69 6E 67 3B 29 56 00 21 00 07 00 08 00 00 00 02
    00 02 00 09 00 0A 00 00 00 01 00 0B 00 0A 00 00
    00 02 00 01 00 0C 00 0D 00 01 00 0E 00 00 00 45
    00 02 00 01 00 00 00 13 2A B7 00 01 2A 11 01 2C
    B5 00 02 2A 11 01 90 B5 00 03 B1 00 00 00 02 00
    0F 00 00 00 0E 00 03 00 00 00 03 00 04 00 05 00
    0B 00 06 00 10 00 00 00 0C 00 01 00 00 00 13 00
    11 00 12 00 00 00 09 00 13 00 14 00 02 00 0E 00
    00 00 5A 00 02 00 03 00 00 00 10 10 64 3C 11 00
    C8 3D B2 00 04 12 05 B6 00 06 B1 00 00 00 02 00
    0F 00 00 00 12 00 04 00 00 00 09 00 03 00 0A 00
    07 00 0C 00 0F 00 0D 00 10 00 00 00 20 00 03 00
    00 00 10 00 15 00 16 00 00 00 03 00 0D 00 17 00
    0A 00 01 00 07 00 09 00 18 00 0A 00 02 00 19 00
    00 00 05 01 00 15 00 00 00 01 00 1A 00 00 00 02
    00 1B
    

    魔数和版本号

    CA FE BA BE 00 00 00 34 00 2C 0A 00 08 00 1C 09
    CA FE BA BE: 前四个字节表示文件类型,java
    00 00: 此版本号: 0
    00 34: 主版本号: 34转十进制是52,对应的jdk是1.8
    

    常量池

                            00 2C 0A 00 08 00 1C 09
    00 07 00 1D 09 00 07 00 1E 09 00 1F 00 20 08 00
    21 0A 00 22 00 23 07 00 24 07 00 25 01 00 01 63
    01 00 01 49 01 00 01 64 01 00 06 3C 69 6E 69 74
    ...
    72 65 61 6D 01 00 07 70 72 69 6E 74 6C 6E 01 00
    15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72
    69 6E 67 3B 29 56
    
    00 2C: 常量池的数量,十进制是44,表示有43个常量,对应上面的字节码文件 Constant pool, 索引是1~44
    
    0A: tag是十进制10,在常量池表中对应CONSTANT_MethodRef_info
    00 08: MethodRef_info 的第一个index,指向 #8 
    00 1C: MethodRef_info 的第二个index,指向 #28
    #1 = Methodref          #8.#28         // java/lang/Object."<init>":()V
    
    09: tag是09,CONSTANT_Fieldref_info
    00 07: CONSTANT_Fieldref_info的第一个index,指向#7
    00 1D: CONSTANT_Fieldref_info的第二个index,指向#29
    #2 = Fieldref           #7.#29         // com/example/demo/HelloWord.c:I
    
    09: tag是09,CONSTANT_Fieldref_info
    00 07: #7
    00 1E: #30
    #3 = Fieldref           #7.#30         // com/example/demo/HelloWord.d:I
    
    09: tag是09,CONSTANT_Fieldref_info
    00 1F: #31
    00 20: #32
    #4 = Fieldref           #31.#32        // java/lang/System.out:Ljava/io/PrintStream;
    
    08: tag是08,CONSTANT_String_info
    00 21: #33
    #5 = String             #33            // hello word
    
    0A: tag是10,CONSTANT_MethodRef_info
    00 22: #34
    00 23: #35
    #6 = Methodref          #34.#35        // java/io/PrintStream.println:(Ljava/lang/String;)V
    
    07: tag是7,CONSTANT_Class_info
    00 24: #36
    #7 = Class              #36            // com/example/demo/HelloWord
    
    07: tag是7,CONSTANT_Class_info
    00 25: #37
    #8 = Class              #37            // java/lang/Object
    
    01: tag是1,CONSTANT_Utf8_info
    00 01: length为1
    63: 读取一个byte,63 对应的 ASCII表 字符为 c
    #9 = Utf8               c
    
    01: tag是1,CONSTANT_Utf8_info
    00 01: length为1
    49: 读取一个byte,49 对应的 ASCII表 字符为 I,表示是int类型
    #10 = Utf8               I
    
    01: tag是1,CONSTANT_Utf8_info
    00 01: length为1
    64: 读取一个byte,64 对应的 ASCII表 字符为 d
    #11 = Utf8               d
    
    01: tag是1,CONSTANT_Utf8_info
    00 06: length为6
    3C 69 6E 69 74 3E: 读取6个byte,对应的 ASCII表 拼接为 <init>
    #12 = Utf8               <init>
    
    ...以此类推
    
    01: utf8
    00
    15: length 21 个byte
    28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56: 拼接为 (Ljava/lang/String;)V
    #43 = Utf8               (Ljava/lang/String;)V
    

    访问标志和类全限定名,父类全限定名,接口集合等

    69 6E 67 3B 29 56 00 21 00 07 00 08 00 00 00 02
    00 21: 访问标志位 0021 是0001和0020的并集,表示 ACC_PUBLIC 并 ACC_SUPER,表示的是父类 Object,java中所有类都有共同父类Object,是在编译时给你加上的
    00 07: This Class 的全限定名,在常量池中查找 #7 得到 com/example/demo/HelloWord
    00 08: 父类的全限定名,在常量池中查找 #8 得到 java/lang/Object
    00 00: interface数量,0000表示接口数为0,如果不为0,那么会有interfaces接口表
    

    字段

    69 6E 67 3B 29 56 00 21 00 07 00 08 00 00 00 02
    00 02 00 09 00 0A 00 00 00 01 00 0B 00 0A 00 00
    
    00 02: 字段表集合,表示有两个字段
    
    00 02: field_access_flags,0002表示 ACC_PRIVATE
    00 09: field_table name_index,#9 查询常量池得 c
    00 0A: field_table descriptor_index, 描述符, 10,查询常量池得 I,int类型
    00 00: field_table attributes_count,属性表,为0
    
    00 01: field_access_flags,0002表示 ACC_PUBLIC
    00 0B: field_table name_index,#11 查询常量池得 d
    00 0A: field_table descriptor_index, 描述符, 10,查询常量池得 I,int类型
    00 00: field_table attributes_count,属性表,为0
    

    方法

    00 02 00 01 00 0C 00 0D 00 01 00 0E 00 00 00 45
    00 02: 方法数,两个,构造方法和main方法
    
    第一个方法
    00 02 00 01 00 0C 00 0D 00 01 00 0E 00 00 00 45
    ""方法属性""
    00 01: method_access_flags, ACC_PUBLIC
    00 0C: method_table name_index #12 <init>
    00 0D: method_table descriptor_index #13 ()V 无参无返回值
    00 01: attribute_count 1个属性
    00 0E: 属性名 #14 Code
    00 00 00 45 : 属性长度 69
    ""Code""
    00 02 00 01 00 00 00 13 2A B7 00 01 2A 11 01 2C
    B5 00 02 2A 11 01 90 B5 00 03 B1 00 00 00 02 00
    00 02: max_stack 2
    00 01: max_locals 1
    00 00 00 13: code_length 19
    ""函数栈""
    2A B7 00 01 2A 11 01 2C B5 00 02 2A 11 01 90 B5 00 03 B1: code代码,通过[虚拟机字节码指令](https://www.jianshu.com/p/e6d4e0f01228)查找具体释义
    2A: 0 aload_0(将第一个引用变量推送到栈顶)
    B7: 1 invokespecial(调用父类构造方法)
    00: 空
    01: aconst_null,将null推送到栈顶
    2A: 4 aload_0(将第一个引用变量推送到栈顶)
    11: 5 sipush 将一个short值带符号扩展成int推送至栈顶
    01: aconst_null,将null推送到栈顶
    2C: 将第3个引用类型本地变量推送至栈顶
    B5: 8 putfield, 给对象字段赋值
    00: 空
    02: iconst_m1, 将-1推送到栈顶
    2A: 11 aload_0(将第一个引用变量推送到栈顶)
    11: 12 sipush 将一个short值带符号扩展成int推送至栈顶
    01: aconst_null,将null推送到栈顶
    90: d2f, (float) a, 将栈顶double类型值转换为float类型值,并将结果压入栈顶
    B5: 15 putfield, 给对象字段赋值
    00: 空
    03: iconst_0, 将 0(int)推送至栈顶
    B1: 18 return, void函数返回
    
    ""两个属性表""
                                     00 00 00 02 00
    0F 00 00 00 0E 00 03 00 00 00 03 00 04 00 05 00
    0B 00 06 00 10 00 00 00 0C 00 01 00 00 00 13 00
    11 00 12 00 00 00 09 00 13 00 14 00 02 00 0E 00
    00 00: exception_table_length=0
    00 02: attributes_count=2(Code属性表内部还含有2个属性表),
    
    00 0F: attribute_name_index,转10进制为15,查找常量池 #15 为 LineNumberTable
    00 00 00 0E: LineNumberTable表长度,16
    00 03: line_number_table_length, line_number_info有3个
    00 00: start_pc,字节码行号,0
    00 03: 3
    00 04: 4
    00 05: 5
    00 04: 4
    00 0B: 11
    00 06: 6
    
    00 10: #16,LocalVariableTable
    00 00 00 0C: attribute_length,12
    00 01: local_variable_table_length,表示local_variable_info数量为1
    00 00: start_pc,字节码行号0
    00 13: length 19
    00 11: name_index,#17 -> this
    00 12: descriptor_index,#18 -> Lcom/example/demo/HelloWord;
    00 00: index,0
    
    第二个方法(大部分是重复方法一的内容,主要目的是对得上)
                   00 09 00 13 00 14 00 02 00 0E 00
    00 00 5A 00 02 00 03 00 00 00 10 10 64 3C 11 00
    00 09: 访问标志位 ACC_PUBLIC, ACC_STATIC
    00 13: 方法名 #19 main
    00 14: 描述符 #20 ([Ljava/lang/String;)V
    00 02: 属性表 2个属性
    00 0E: 属性名 #14 code
    00 00 00 5A: 属性长度 90
    ""Code属性""
    00 02: stack = 2
    00 03: locals = 3
    00 00 00 10: 属性长度,16
    ""函数栈""
                                     10 64 3C 11 00
    C8 3D B2 00 04 12 05 B6 00 06 B1 00 00 00 02 00
    0.10: bipush 将一个byte值带符号扩展成int推送至栈顶
    1.64: isub 将栈顶两int类型数相减,并将结果压入栈顶
    2.3C: istore_1 将栈顶int类型值保存到局部变量1中
    3.11: sipush 将一个short值带符号扩展成int推送至栈顶
    4.00
    5.C8: goto_w 无条件跳转到指定位置(宽索引)
    6.3D: istore_2 将栈顶int类型值保存到局部变量2中
    7.B2: getstatic 获取静态字段的值,并将其引用压入栈顶
    8.00
    9.04: iconst_1 将 1(int)推送至栈顶
    10.12: ldc 将int、float或String型常量值从常量池中推送至栈顶
    11.05: iconst_2 将 2(int)推送至栈顶
    12.B6: invokevirtual 运行时方法绑定调用方法
    13.00
    14.06: iconst_3 将 3(int)推送至栈顶
    15.B1: return void函数返回
    ""属性表""
    00 00: exception_table_length=0
    00 02: attributes_count=2(Code属性表内部还含有2个属性表)
    
    0F 00 00 00 12 00 04 00 00 00 09 00 03 00 0A 00
    07 00 0C 00 0F 00 0D 00 10 00 00 00 20 00 03 00
    00 00 10 00 15 00 16 00 00 00 03 00 0D 00 17 00
    0A 00 01 00 07 00 09 00 18 00 0A 00 02 00 19 00
    00 0F: attribute_name_index #15 为 LineNumberTable
    00 00 00 12: length 18
    00 04: line_number_table_length, line_number_info有4个
    00 00: start_pc, 0
    00 09: line_number, 9
    00 03: start_pc, 3
    00 0A: line_number, 10
    00 07: start_pc, 7
    00 0C: line_number, 12
    00 0F: start_pc, 13
    00 0D: line_number, 15
    
    00 10: #16,LocalVariableTable
    00 00 00 20: length 32
    00 03: local_variable_info数量为3
    00 00: start_pc 0
    00 10: length,16
    00 15: name_index #21 -> args
    00 16: descriptor_index #22 -> [Ljava/lang/String;
    00 00: index 0
    00 03: start_pc 3
    00 0D: length, 13
    00 17: name_index #23 -> a
    00 0A: descriptor_index #10 -> I
    00 01: index 1
    00 07: start_pc 7
    00 09: length 9
    00 18: name_index #24 -> b
    00 0A: descriptor_index #10 -> I
    00 02: index 2
    
    ""MethodParameters属性""
                                           00 19 00
    00 00 05 01 00 15 00 00 00 01 00 1A 00 00 00 02
    00 19: 属性名 #25 MethodParameters, 记录方法的各个形参名称和信息
    00 00 00 05: length, 5
    01: parameters_count, 1个参数
    00 15: parameter.name_index, #21 -> args
    00 00: access_flags,参数状态(0010被final修饰;1000编译器生成的;8000隐式定义的,如this关键字)
    

    Attribute

    00 00 05 01 00 15 00 00 00 01 00 1A 00 00 00 02
    00 1B
    00 01: 有1个Attributes
    00 1A: name_index #26 -> SourceFile
    00 00 00 02: attribute_length=2
    00 1B: source_index #27 -> HelloWord.java
    

    参考
    这一次,彻底弄懂「Java字节码文件」 - 知乎 (zhihu.com)
    《深入理解Java虚拟机》
    一文让你明白Java字节码 - 简书 (jianshu.com)
    ASCII码一览表,ASCII码对照表 (biancheng.net)
    Gradle 插件 + ASM 实战 - JVM 虚拟机加载 Class 原理 - 掘金 (juejin.cn)

    相关文章

      网友评论

          本文标题:1.Java字节码文件分析

          本文链接:https://www.haomeiwen.com/subject/wyroldtx.html