1.Java字节码文件分析

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)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容