读懂 JVM 字节码

这篇文章是我为了方便以后的查找和快速记忆,在自己理解的基础上总结而成的文章,不是为新手准备的;

如果真的想轻松理解这篇文章里描述的东西,那么推荐你先把文章后面的参考链接仔细研读一遍。

0x00:字节码文件结构

总览:

ClassFile {
    u4              magic;                                  // 文件幻数
    u2              minor_version;                          // 次版本号
    u2              major_version;                          // 主版本号
    u2              constant_pool_count;                    // 常量实体数量 + 1
    cp_info         contant_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];           // 属性实体表
}

解释:

  • 字节码文件结构总览从上到下来看,就是每项结构在文件中的出现顺序.第一行就是文件开头;
  • 字节码文件结构总览从左到右来看,最左侧表示每项所占字节大小(有的大小不固定),右侧是每项结构说明;
  • u + 数字, 代表占用的空间大小也叫类型. 第一个 u4 就代表此项结构在字节码文件中占用 4 个字节(bytes)

实体结构从上至下,依次为:

cp_info 结构

由不固定数量的常量实体组成;
常量实体表见下文 "0x01 —— 14种数据类型常量结构表"

class access_flags 表

访问控制标识符 解释
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_FINAL 0x0010 Declared final; no subclasses allowed.
ACC_SUPER 0x0020 Treat superclass methods specially when invoked by the invokespecial instruction.
ACC_INTERFACE 0x0200 Is an interface, not a class.
ACC_ABSTRACT 0x0400 Declared abstract; must not be instantiated.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
ACC_ANNOTATION 0x2000 Declared as an annotation type.
ACC_ENUM 0x4000 Declared as an enum type.

field_info 结构

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

field_info access_flags 表

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; usable only within the defining class.
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; never directly assigned to after object construction (JLS §17.5).
ACC_VOLATILE 0x0040 Declared volatile; cannot be cached.
ACC_TRANSIENT 0x0080 Declared transient; not written or read by a persistent object manager.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.
ACC_ENUM 0x4000 Declared as an element of an enum.

method_info 结构

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

method_info access_flags 表

Flag Name Value Interpretation
ACC_PUBLIC 0x0001 Declared public; may be accessed from outside its package.
ACC_PRIVATE 0x0002 Declared private; accessible only within the defining class.
ACC_PROTECTED 0x0004 Declared protected; may be accessed within subclasses.
ACC_STATIC 0x0008 Declared static.
ACC_FINAL 0x0010 Declared final; must not be overridden (§5.4.5).
ACC_SYNCHRONIZED 0x0020 Declared synchronized; invocation is wrapped by a monitor use.
ACC_BRIDGE 0x0040 A bridge method, generated by the compiler.
ACC_VARARGS 0x0080 Declared with variable number of arguments.
ACC_NATIVE 0x0100 Declared native; implemented in a language other than Java.
ACC_ABSTRACT 0x0400 Declared abstract; no implementation is provided.
ACC_STRICT 0x0800 Declared strictfp; floating-point mode is FP-strict.
ACC_SYNTHETIC 0x1000 Declared synthetic; not present in the source code.

attribute_info 结构

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

attribute 结构

Attribute Details
ConstantValue §4.7.2
Code §4.7.3
StackMapTable §4.7.4
Exceptions §4.7.5
InnerClasses §4.7.6
EnclosingMethod §4.7.7
Synthetic §4.7.8
Signature §4.7.9
SourceFile §4.7.10
SourceDebugExtension §4.7.11
LineNumberTable §4.7.12
LocalVariableTable §4.7.13
LocalVariableTypeTable §4.7.14
Deprecated §4.7.15
RuntimeVisibleAnnotations §4.7.16
RuntimeInvisibleAnnotations §4.7.17
RuntimeVisibleParameterAnnotations §4.7.18
RuntimeInvisibleParameterAnnotations §4.7.19
AnnotationDefault §4.7.20
BootstrapMethods §4.7.21

0x01:相关项解释

文件幻数:

挺有意思,值为十六进制的 cafe babe (咖啡馆宝贝)

主版本与适配的JDK版本:

major_version (十进制) major_version (十六进制) JDK version
46 0x2e 1.2
47 0x2f 1.3
48 0x30 1.4
49 0x31 1.5
50 0x32 1.6
51 0x33 1.7
52 0x34 1.8

14种数据类型常量结构表:

常量表

  • 总共十四种常量实体类型,每一种都有一个tag项,tag项的值(全部占一字节)标识它是哪种类型的常量
  • 常量池中存放着字符串常量、类和接口的全限定名引用、字段的名称和修饰符引用和方法的名称和修饰符引用
  • 一旦确定常量实体的种类,那么它所占用的总字节数就可以根据这种常量的固定结构计算出来

类索引

  • this_class 保存当前类的全限定名的索引
  • super_class 保存当前类的父类的全限定名的索引

0x02:手工分析字节码

一:准备工作

  1. 写一段简单代码保存为 hello.java 文件.
public class hello {
    protected String w = "world";

    public static String test(String str){
        return str;
    }

    public static void main(String[] args) {
        System.out.print("LandGrey");
    }
}
  1. 用命令 javac -target 1.6 -source 1.6 hello.javahello.java 编译为 class 字节码文件.

  2. winhex 观察 hello.class 字节码文件.

二:开始解读

对于下图中的标记: class-note-1

意义分别如下:

标记 意义
1 CA FE BA BE 字节码文件幻数,固定值
2 00 00 minor_version 值为零
3 00 32 major_version 值为 0x32,对应 JVM 版本为 1.6
4 00 25 constant_pool_count 值 为 0x25,代表接下来的常量池中共有 36 个常量实体

三:常量池解读

常量池中36 个具体的常量实体如下图标记所示: class-note-4

  • 第一个蓝色框 00 25 后面的所有被不同颜色框选中的为具体的 36 个常量实体
  • 被红色框选中的,都是 CONSTANT_Utf8_info 类型实体,它们每个后面都跟着被 length 长度的字符串(即后面紧跟着的未被任何颜色框中的部分)
  • 注意由于换行原因,最右侧的一个框可能和它下面一行的开始的框是一个整体,属于一个常量实体

四:36个常量实体次序表

根据常量池解读,依照次序列出常量实体表,方便后续的整体分析. 常量实体过长的,用 ~ 表示中间部分省略.

次序 常量实体 类别 说明
1 0A 00 08 00 15 CONSTANT_Methodref_info 索引分别指向第8个常量,第21个常量
2 08 00 16 CONSTANT_String_info 索引指向第22个字符串常量world
3 09 00 07 00 17 CONSTANT_Fieldref_info 索引分别指向第7个常量,第23个常量
4 09 00 18 00 19 CONSTANT_Fieldref_info 索引分别指向第24个常量,第25个常量
5 08 00 1A CONSTANT_String_info 索引指向第26个字符串常量LandGrey
6 0A 00 1B 00 1C CONSTANT_Methodref_info 索引分别指向第27个常量,第28个常量
7 07 00 1D CONSTANT_Class_info 索引指向第29个常量hello
8 07 00 1E CONSTANT_Class_info 索引指向第30个常量java/lang/Object
9 01 00 01 CONSTANT_Utf8_info 表示后面跟着的字符串长度为1个字节
77 字符串w
10 01 00 12 CONSTANT_Utf8_info 表示后面跟着的字符串长度为18个字节
4C6A ~ 673B 字符串Ljava/lang/String;
11 01 00 06 CONSTANT_Utf8_info 表示后面跟着的字符串长度为6个字节
3C696E69743E 字符串<init>
12 01 00 03 CONSTANT_Utf8_info 表示后面跟着的字符串长度为3个字节
28 29 56 字符串()V
13 01 00 04 CONSTANT_Utf8_info 表示后面跟着的字符串长度为4个字节
436F6465 字符串Code
14 01 00 0F CONSTANT_Utf8_info 表示后面跟着的字符串长度为15个字节
4C69 ~ 6C65 字符串LineNumberTable
15 01 00 04 CONSTANT_Utf8_info 表示后面跟着的字符串长度为4个字节
74657374 字符串test
16 01 00 26 CONSTANT_Utf8_info 表示后面跟着的字符串长度为38个字节
284C ~ 673B 字符串(Ljava/lang/String;)Ljava/lang/String;
17 01 00 04 CONSTANT_Utf8_info 表示后面跟着的字符串长度为4个字节
6D61696E 字符串main
18 01 00 16 CONSTANT_Utf8_info 表示后面跟着的字符串长度为22个字节
285B ~ 2956 字符串([Ljava/lang/String;)V
19 01 00 0A CONSTANT_Utf8_info 表示后面跟着的字符串长度为10个字节
536F ~ 6C65 字符串SourceFile
20 01 00 0A CONSTANT_Utf8_info 表示后面跟着的字符串长度为10个字节
6865 ~ 7661 字符串hello.java
21 0C 00 0B 00 0C CONSTANT_NameAndType_info 索引分别指向第11个常量,第12个常量
22 01 00 05 CONSTANT_Utf8_info 表示后面跟着的字符串长度为5个字节
776F726C64 字符串world
23 0C 00 09 00 0A CONSTANT_NameAndType_info 索引分别指向第9个常量,第10个常量
24 07 00 1F CONSTANT_Class_info 索引指向第31个常量
25 0C 00 20 00 21 CONSTANT_NameAndType_info 索引分别指向第32个常量,第33个常量
26 01 00 08 CONSTANT_Utf8_info 表示后面跟着的字符串长度为8个字节
4C61 ~ 6579 字符串LandGrey
27 07 00 22 CONSTANT_Class_info 索引指向第34个常量
28 0C 00 23 00 24 CONSTANT_NameAndType_info 索引分别指向第35个常量,第36个常量
29 01 00 05 CONSTANT_Utf8_info 表示后面跟着的字符串长度为5个字节
68656C6C6F 字符串hello
30 01 00 10 CONSTANT_Utf8_info 表示后面跟着的字符串长度为16个字节
6A61 ~ 6374 字符串java/lang/Object
31 01 00 10 CONSTANT_Utf8_info 表示后面跟着的字符串长度为16个字节
6A61 ~ 656D 字符串java/lang/System
32 01 00 03 CONSTANT_Utf8_info 表示后面跟着的字符串长度为3个字节
6F7574 字符串out
33 01 00 15 CONSTANT_Utf8_info 表示后面跟着的字符串长度为21个字节
4C6A ~ 6D3B 字符串Ljava/io/PrintStream;
34 01 00 13 CONSTANT_Utf8_info 表示后面跟着的字符串长度为19个字节
6A61 ~ 616D 字符串java/io/PrintStream
35 01 00 05 CONSTANT_Utf8_info 表示后面跟着的字符串长度为5个字节
7072696E74 字符串print
36 01 00 15 CONSTANT_Utf8_info 表示后面跟着的字符串长度为21个字节
284C ~ 2956 字符串(Ljava/lang/String;)V

五:解读其余字节码

  • 严格按照本文 0x00 部分和 参考链接1的字节码文件结构进行字节码解读
  • 其余很多结构都会引用 四:36个常量实体次序表 中的常量实体
  • 剩余部分内容太多,所以只列出关键的基本引用信息
  • start_pc代表字节码偏移量(方法第几条指令),line_number代表源码行号,调试信息,非必须项
值           实体                  含义
0021        access_flags        ACC_PUBLIC + ACC_SUPER
0007        this_class          指向字符串 hello
0008        super_class         指向字符串 java/lang/Object
0000        interfaces_count    无接口,所以后面也无接口实体列表
0001        fields_count        字段数量为1个
0004        field_info[1]       第一个字段的access_flags标志为 ACC_PROTECTED
0009        field_info[1]       第一个字段的name_index指向字符串 w
000A        field_info[1]       第一个字段的descriptor_index指向字符串 Ljava/lang/String;
0000        field_info[1]       第一个字段的attributes_count数量为零
0003        methods_count       方法数量为3个
0001        method_info[1]      第一个方法的access_flags标志为 ACC_PUBLIC
000B        method_info[1]      第一个方法的name_index指向字符串 <init> (方法)
000C        method_info[1]      第一个方法的descriptor_index指向字符串 ()V
0001        method_info[1]      第一个方法的attributes_count数量为1个
000D        m[1]attributes[1]   第一个方法的Code属性的attribute_name_index指向字符串 Code
00000027    m[1]attributes[1]   第一个方法的Code属性的attribute_length值为39(后面取39个字节)
0002        m[1]attributes[1]   第一个方法的Code属性的max_stack值为2
0001        m[1]attributes[1]   第一个方法的Code属性的max_locals值为1
0000000B    m[1]attributes[1]   第一个方法的Code属性的code_length值为11
2AB700012A1202B50003B1      m[1]attributes[1]   第一个方法的Code属性的code值
0000        m[1]attributes[1]   第一个方法的Code属性的exception_table_length值为0
0001        m[1]attributes[1]   第一个方法的Code属性的attributes_count值为1
000E        m[1]a[1]a[1]        第一个方法的Code属性的第一个属性attribute_name_index指向字符串LineNumberTabl
0000000A    m[1]a[1]a[1]        第一个方法的Code属性的LineNumberTabl属性的attribute_length值为10
0002        m[1]a[1]a[1]        第一个方法的Code属性的LineNumberTabl属性的line_number_table_length值为2
0000 0001   m[1]a[1]a[1]        第一个方法的Code属性的LineNumberTabl属性的第1个start_pc为0 line_number为1
0004 0002   m[1]a[1]a[1]        第一个方法的Code属性的LineNumberTabl属性的第2个start_pc为4 line_number为2
0009        method_info[2]      第二个方法的access_flags标志为 ACC_PUBLIC + ACC_STATIC
000F        method_info[2]      第二个方法的name_index指向字符串 test (方法)
0010        method_info[2]      第二个方法的descriptor_index指向字符串 (Ljava/lang/String;)Ljava/lang/String;
0001        method_info[2]      第二个方法的attributes_count数量为1个
000D        m[2]attributes[1]   第二个方法的Code属性的attribute_name_index指向字符串 Code
0000001A    m[2]attributes[1]   第二个方法的Code属性的attribute_length值为26
0001        m[2]attributes[1]   第二个方法的Code属性的max_stack值为1
0001        m[2]attributes[1]   第二个方法的Code属性的max_locals值为1
00000002    m[2]attributes[1]   第二个方法的Code属性的code_length值为2
2AB0        m[2]attributes[1]   第二个方法的Code属性的code值
0000        m[2]attributes[1]   第二个方法的Code属性的exception_table_length值为0
0001        m[2]attributes[1]   第二个方法的Code属性的attributes_count值为1
000E        m[2]a[1]a[1]        第二个方法的Code属性的第一个属性attribute_name_index指向字符串LineNumberTabl
00000006    m[2]a[1]a[1]        第二个方法的Code属性的LineNumberTabl属性的attribute_length值为6
0001        m[2]a[1]a[1]        第二个方法的Code属性的LineNumberTabl属性的line_number_table_length值为1
0000 0005   m[2]a[1]a[1]        第二个方法的Code属性的LineNumberTabl属性的第1个start_pc为0 line_number为5
0009        method_info[3]      第三个方法的access_flags标志为 ACC_PUBLIC + ACC_STATIC
0011        method_info[3]      第三个方法的name_index指向字符串 main
0012        method_info[3]      第三个方法的descriptor_index指向字符串 ([Ljava/lang/String;)V
0001        method_info[3]      第三个方法的attributes_count数量为1个
000D        m[3]attributes[1]   第三个方法的Code属性的attribute_name_index指向字符串 Code
00000025    m[3]attributes[1]   第三个方法的Code属性的attribute_length值为37
0002        m[3]attributes[1]   第三个方法的Code属性的max_stack值为2
0001        m[3]attributes[1]   第三个方法的Code属性的max_locals值为1
00000009    m[3]attributes[1]   第三个方法的Code属性的code_length值为9
B200041205B60006B1      m[3]attributes[1]   第二个方法的Code属性的code值
0000        m[3]attributes[1]   第三个方法的Code属性的exception_table_length值为0
0001        m[3]attributes[1]   第三个方法的Code属性的attributes_count值为1
000E        m[3]a[1]a[1]        第三个方法的Code属性的第一个属性attribute_name_index指向字符串LineNumberTabl
0000000A    m[3]a[1]a[1]        第三个方法的Code属性的LineNumberTabl属性的attribute_length值为10
0002        m[3]a[1]a[1]        第三个方法的Code属性的LineNumberTabl属性的line_number_table_length值为2
0000 0009   m[3]a[1]a[1]        第三个方法的Code属性的LineNumberTabl属性的第1个start_pc为0 line_number为9
0008 000A   m[3]a[1]a[1]        第三个方法的Code属性的LineNumberTabl属性的第2个start_pc为8 line_number为10
0001        attributes_count    attributes_count值为1
0013        attributes[1]       第一个属性的attribute_name_index指向 SourceFile 字符串
00000002    attributes[1]       第一个属性的attribute_length为2
0014        attributes[1]       第一个属性的sourcefile_index指向常量池中第20个常量  hello.java

0x03:javap分析字节码

人工用二进制查看器分析class 字节码既麻烦又复杂,所以使用Oracle官方提供的 javap工具:

>javap -v -p -l hello.class

Classfile /path/to/hello.class
  Last modified 2019-9-18; size 562 bytes
  MD5 checksum 2f562da266fabaa7affd5e0cca172152
  Compiled from "hello.java"
public class hello
  minor version: 0
  major version: 50
  flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
   #1 = Methodref          #8.#21         // java/lang/Object."<init>":()V
   #2 = String             #22            // world
   #3 = Fieldref           #7.#23         // hello.w:Ljava/lang/String;
   #4 = Fieldref           #24.#25        // java/lang/System.out:Ljava/io/PrintStream;
   #5 = String             #26            // LandGrey
   #6 = Methodref          #27.#28        // java/io/PrintStream.print:(Ljava/lang/String;)V
   #7 = Class              #29            // hello
   #8 = Class              #30            // java/lang/Object
   #9 = Utf8               w
  #10 = Utf8               Ljava/lang/String;
  #11 = Utf8               <init>
  #12 = Utf8               ()V
  #13 = Utf8               Code
  #14 = Utf8               LineNumberTable
  #15 = Utf8               test
  #16 = Utf8               (Ljava/lang/String;)Ljava/lang/String;
  #17 = Utf8               main
  #18 = Utf8               ([Ljava/lang/String;)V
  #19 = Utf8               SourceFile
  #20 = Utf8               hello.java
  #21 = NameAndType        #11:#12        // "<init>":()V
  #22 = Utf8               world
  #23 = NameAndType        #9:#10         // w:Ljava/lang/String;
  #24 = Class              #31            // java/lang/System
  #25 = NameAndType        #32:#33        // out:Ljava/io/PrintStream;
  #26 = Utf8               LandGrey
  #27 = Class              #34            // java/io/PrintStream
  #28 = NameAndType        #35:#36        // print:(Ljava/lang/String;)V
  #29 = Utf8               hello
  #30 = Utf8               java/lang/Object
  #31 = Utf8               java/lang/System
  #32 = Utf8               out
  #33 = Utf8               Ljava/io/PrintStream;
  #34 = Utf8               java/io/PrintStream
  #35 = Utf8               print
  #36 = Utf8               (Ljava/lang/String;)V
{
  protected java.lang.String w;
    descriptor: Ljava/lang/String;
    flags: ACC_PROTECTED

  public hello();
    descriptor: ()V
    flags: 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: ldc           #2                  // String world
         7: putfield      #3                  // Field w:Ljava/lang/String;
        10: return
      LineNumberTable:
        line 1: 0
        line 2: 4

  public static java.lang.String test(java.lang.String);
    descriptor: (Ljava/lang/String;)Ljava/lang/String;
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: areturn
      LineNumberTable:
        line 5: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: ACC_PUBLIC, ACC_STATIC
    Code:
      stack=2, locals=1, args_size=1
         0: getstatic     #4                  // Field java/lang/System.out:Ljava/io/PrintStream;
         3: ldc           #5                  // String LandGrey
         5: invokevirtual #6                  // Method java/io/PrintStream.print:(Ljava/lang/String;)V
         8: return
      LineNumberTable:
        line 9: 0
        line 10: 8
}
SourceFile: "hello.java"

和手工分析的字节码对比,可以发现很多细节都能对应上,有一种恍然大悟的感觉.

0x04:字节码指令

现在,字节码结构能够大概理解了,但是其中还有三个方法的Code属性的code值,也就是字节码指令没有介绍。这部分内容很庞大,单独拿出来都能写几篇文章了,只做简单介绍.

  • JVM 字节码指令由一个字节长度的操作码零至多个长度的操作数组成.

  • JVM 字节码指令是面向后进先出操作数栈进行入栈出栈操作的.

  • JVM 字节码指令的加载存储是将数据从局部变量表操作数栈进行交替传输的过程

  • 每个变量都占局部变量区中的一个变量槽(slot),long及double会占用两个连续的变量槽

一张数据类型为行,操作指令为列,引用自网络资源的操作指令表如下: opcode

解答下上面案列中3个方法的字节码指令的含义:

第一处字节码指令

Code:
  stack=2, locals=1, args_size=1
     0: aload_0
     1: invokespecial #1           // Method java/lang/Object."<init>":()V
     4: aload_0
     5: ldc           #2           // String world
     7: putfield      #3           // Field w:Ljava/lang/String;
    10: return
  LineNumberTable:
    line 1: 0
    line 2: 4

指令含义

aload_0             从局部变量数组中加载一个对象引用(this)到操作数栈的栈顶
invokespecial       调用对象实例java.lang.Object的构造方法
aload_0             从局部变量数组中加载一个对象引用到操作数栈的栈顶
ldc                 将常量 "world" 从常量池推送到操作数栈顶
putfield            为实例字段 "w" 赋值 "world"
return              从当前方法返回void

第二处字节码指令

Code:
  stack=1, locals=1, args_size=1
     0: aload_0
     1: areturn
  LineNumberTable:
    line 5: 0

指令含义

aload_0             从局部变量数组中加载一个对象引用到操作数栈的栈顶
areturn             返回栈顶元素              

第三处字节码指令

Code:
  stack=2, locals=1, args_size=1
     0: getstatic     #4     // Field java/lang/System.out:Ljava/io/PrintStream;
     3: ldc           #5     // String LandGrey
     5: invokevirtual #6     // Method java/io/PrintStream.print:(Ljava/lang/String;)V
     8: return
  LineNumberTable:
    line 9: 0
    line 10: 8

指令含义

getstatic               将static引用变量推送到栈顶
ldc                     将常量 "LandGrey" 从常量池推送到操作数栈顶
invokevirtual           调用对象的实例方法
return                  从当前方法返回void

0x05:参考链接

  1. Chapter 4. The class File Format
  2. 深入理解JVM之Java字节码(.class)文件详解
  3. The Java® Virtual Machine Specification
  4. 一文让你明白Java字节码
  5. JAVA基础回顾-字节码指令
  6. Java字节码浅析(—)

附件

文中分析的 hello.class 源文件

标签 

评论