无关性的基石

Write One,Run Anywhere

  • 平台无关性

各种不同平台的虚拟机和所有平台都使用的程序存储格式——字节码(Byte Code)是构成平台无关性的基石。

  • 语言无关性

虚拟机和程序存储格式也是构成语言无关性的基石。当一门语言能够编译生成 JVM 识别的class文件,就能够在JVM上运行。

Class 类文件结构

Class文件是一组以一个字节为基本单元的二进制流,各个数据之前是有序的,是紧凑。

class文件只存在两种数据类型:无符号数和表。

  • 无符号数:基本类型,u1,u2,u4,u8分别代表1,2,4,8字节
  • 表:多个无符号数或其他表构成的复合结构

类型 名称 数量

类型 名称 数量
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_flag 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
methos_info mothod methods_count
u2 attributes_count 1
attribute_info attributes attributes_count

初识字节码

首先看一个简单的程序:

  1. package com.yangll.jvm.bytecode;
  2. public class Test01 {
  3. private int a = 1;
  4. public int getA() {
  5. return a;
  6. }
  7. public void setA(int a) {
  8. this.a = a;
  9. }
  10. }

反编译生成的class文件:

javap -c D:\project\java\build\classes\java\main\com\yangll\jvm\bytecode\Test01.class

  1. Compiled from "Test01.java"
  2. public class com.yangll.jvm.bytecode.Test01 {
  3. public com.yangll.jvm.bytecode.Test01();
  4. Code:
  5. 0: aload_0
  6. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  7. 4: aload_0
  8. 5: iconst_1
  9. 6: putfield #2 // Field a:I
  10. 9: return
  11. public int getA();
  12. Code:
  13. 0: aload_0
  14. 1: getfield #2 // Field a:I
  15. 4: ireturn
  16. public void setA(int);
  17. Code:
  18. 0: aload_0
  19. 1: iload_1
  20. 2: putfield #2 // Field a:I
  21. 5: return
  22. }
  1. public com.yangll.jvm.bytecode.Test01();
  2. Code:
  3. 0: aload_0
  4. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  5. 4: aload_0
  6. 5: iconst_1
  7. 6: putfield #2 // Field a:I
  8. 9: return

默认的构造方法 aload_0 表示将this压入栈顶 invokespecial 表示调用父类的构造器,也就是Object的构造方法

为什么要调用两次aload_0 ?

javap 命令还有一个参数—verbose,可以查看更详细的信息:

javap -verbose D:\project\java\build\classes\java\main\com\yangll\jvm\bytecode\Test01.class

  1. Classfile /D:/project/java/build/classes/java/main/com/yangll/jvm/bytecode/Test01.class
  2. Last modified 2020-4-24; size 490 bytes
  3. MD5 checksum b767989cc41189e99e1255dc313c5d0c
  4. Compiled from "Test01.java"
  5. public class com.yangll.jvm.bytecode.Test01
  6. minor version: 0
  7. major version: 52
  8. flags: ACC_PUBLIC, ACC_SUPER
  9. Constant pool:
  10. #1 = Methodref #4.#20 // java/lang/Object."<init>":()V
  11. #2 = Fieldref #3.#21 // com/yangll/jvm/bytecode/Test01.a:I
  12. #3 = Class #22 // com/yangll/jvm/bytecode/Test01
  13. #4 = Class #23 // java/lang/Object
  14. #5 = Utf8 a
  15. #6 = Utf8 I
  16. #7 = Utf8 <init>
  17. #8 = Utf8 ()V
  18. #9 = Utf8 Code
  19. #10 = Utf8 LineNumberTable
  20. #11 = Utf8 LocalVariableTable
  21. #12 = Utf8 this
  22. #13 = Utf8 Lcom/yangll/jvm/bytecode/Test01;
  23. #14 = Utf8 getA
  24. #15 = Utf8 ()I
  25. #16 = Utf8 setA
  26. #17 = Utf8 (I)V
  27. #18 = Utf8 SourceFile
  28. #19 = Utf8 Test01.java
  29. #20 = NameAndType #7:#8 // "<init>":()V
  30. #21 = NameAndType #5:#6 // a:I
  31. #22 = Utf8 com/yangll/jvm/bytecode/Test01
  32. #23 = Utf8 java/lang/Object
  33. {
  34. public com.yangll.jvm.bytecode.Test01();
  35. descriptor: ()V
  36. flags: ACC_PUBLIC
  37. Code:
  38. stack=2, locals=1, args_size=1
  39. 0: aload_0
  40. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  41. 4: aload_0
  42. 5: iconst_1
  43. 6: putfield #2 // Field a:I
  44. 9: return
  45. LineNumberTable:
  46. line 3: 0
  47. line 5: 4
  48. LocalVariableTable:
  49. Start Length Slot Name Signature
  50. 0 10 0 this Lcom/yangll/jvm/bytecode/Test01;
  51. public int getA();
  52. descriptor: ()I
  53. flags: ACC_PUBLIC
  54. Code:
  55. stack=1, locals=1, args_size=1
  56. 0: aload_0
  57. 1: getfield #2 // Field a:I
  58. 4: ireturn
  59. LineNumberTable:
  60. line 9: 0
  61. LocalVariableTable:
  62. Start Length Slot Name Signature
  63. 0 5 0 this Lcom/yangll/jvm/bytecode/Test01;
  64. public void setA(int);
  65. descriptor: (I)V
  66. flags: ACC_PUBLIC
  67. Code:
  68. stack=2, locals=2, args_size=2
  69. 0: aload_0
  70. 1: iload_1
  71. 2: putfield #2 // Field a:I
  72. 5: return
  73. LineNumberTable:
  74. line 13: 0
  75. line 14: 5
  76. LocalVariableTable:
  77. Start Length Slot Name Signature
  78. 0 6 0 this Lcom/yangll/jvm/bytecode/Test01;
  79. 0 6 1 a I
  80. }
  81. SourceFile: "Test01.java"

使用 javap -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类的方法信息,类变量和成员变量的信息

前面说过,class文件是一个字节流文件,我们通过反编译看到的都是比较直观的信息,那怎样看到对应的字节流呢?Windows中,可以使用 WinHex 软件打开class文件,即可看到对应的字节流。使用 WinHex 打开得到如下所示:

下载 winHex 软件,打开class文件可以得到:
捕获.PNG

CA FE BA BE** 00 00 00 34 00 18 0A 00 04 00 14 09 00 03 00 15 07 00 16 07 00 17 01 00 01 61 01 00 01 49 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 20 4C 63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31 3B 01 00 04 67 65 74 41 01 00 03 28 29 49 01 00 04 73 65 74 41 01 00 04 28 49 29 56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0B 54 65 73 74 30 31 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 1E 63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 03 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 03 00 01 00 07 00 08 00 01 00 09 00 00 00 38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 03 00 04 00 05 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 09 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 10 00 11 00 01 00 09 00 00 00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 0D 00 05 00 0E 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D 00 00 00 00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00 **

结合Class文件结构可知:

魔数(类型:u4)(数量1)

4个字节,值为0xCAFEBABE

次版本号(u2)(1)

2个字节,值为0x0000

主版本号(u2)(1)

2个字节,值为0x0034,即52,表示使用JDK1.8

常量池数量(u2)(1)

2个字节,值为0x0018,即24.但是通过反编译指令 -``verbose 得到的结果可知,常量池数量只有 #1 ~ #23,共23个。那剩下的一个呢?其实Java设计者在设计这门语言的时候,将第 0 项空了出来,便是不指向任何任何常量,也就是Java中 null 的概念。

常量池(cp_info)(常量池数量)

一个Java类定义的很多信息都是由常量池来维护和描述的。常量池中主要存放两大类常量:字面量和符号引用。

  1. 字面量:字符串文本、final变量
  2. 符号引用
  • 类和接口的全限定名
  • 字段的名称和描述符
  • 方法的名称和描述符

class文件中的11中常量结构

常量 项目 类型 描述
CONSTANT_utf8_info tag u1 值为1
length u2 UTF-8编码字符串长度
bytes u1 长度为length的UFT-8编码的字符串
CONSTANT_Integer_info tag u1 值为3
bytes u4 按照高位在前存储int值
CONSTANT_Float_info tag u1 值为4
bytes u4 按照高位在前存储float值
CONSTANT_Long_info tag u1 值为5
bytes u8 按照高位在前存储long值
CONSTANT_Double_info tag u1 值为6
bytes u8 按照高位在前存储double值
CONSTANT_Class_info tag u1 值为7
index u2 指向全限定名称常量项的索引
CONSTANT_String_info tag u1 值为8
index u2 指向字符串字面量的索引
CONSTANT_Fieldref_info tag u1 值为9
index u2 指向声明字段的类或接口描述符CONSTANT_Class_info的索引项
index u2 指向字段描述符CONSTANT_NameAndType_info的索引项
CONSTANT_Methodref_info tag u1 值为10
index u2 指向声明方法的类描述符CONSTANT_Class_info的索引项
index u2 指向名称及描述符CONSTANT_NameAndType_info的索引项
CONSTANT_InterfaceMethodref_info tag u1 值为11
index u2 指向声明方法的接口描述符CONSTANT_Class_info的索引项
index u2 指向名称及类型描述符CONSTANT_NameAndType_info的索引项
CONSTANT_NameAndType_info tag u1 值为12
index u2 指向该字段或方法名称常量项索引项
index u2 指向该字段或方法描述符常量项索引项
CONSTANT_MethodHandle_info tag u1 值为15
reference_kind u1 值必须在1-9之间(包括1和9),它决定了方法句柄的类型。方法句柄类型的值表示方法的字节码行为
reference_index u2 值必须是对常量池的有效索引
CONSTANT_MethodType_info tag u1 值为16
descriptor_index u2 值必须是对常量池的有效索引,常量池在该索引处的项必须是 CONSTANT_Utf8_info结构,表示方法的描述
CONSTANT_InvokeDynamic_info tag u1 值为18
bootstrap_method_attr_index u2 值必须是对当前Class文件中引导方法表的bootstrap_mothods[]数组的有效索引
name_and_type_index u2 值必须是对常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符

继续分析常量池之前,需要了解一些知识:

  • 在JVM中,对每个变量都有一套对应的描述信息,主要作用是描述字段的数据类型、方法参数列表(包括数量、类型和顺序)与返回值。
    | Java | JVM | 举例 | | :—-: | :—-: | :—-: | | byte | B | | | char | C | | | double | D | | | float | F | | | int | I | | | long | J | | | short | S | | | boolean | Z | | | void | V | | | 引用 | L引用类型; | Ljava/lang/String; | | 数组 | 每个维度使用一个[表示 | int[][] —> [[I | | 方法 | 先参数,后返回值的顺序进行描述。方法按照顺序放在()内 | String get(int id,String name)

    (I, Ljava/lang/String;)Ljava/lang/String; |
  1. Constant pool:
  2. #1 = Methodref #4.#20 // java/lang/Object."<init>":()V
  3. #2 = Fieldref #3.#21 // com/yangll/jvm/bytecode/Test01.a:I
  4. #3 = Class #22 // com/yangll/jvm/bytecode/Test01
  5. #4 = Class #23 // java/lang/Object
  6. #5 = Utf8 a
  7. #6 = Utf8 I
  8. #7 = Utf8 <init>
  9. #8 = Utf8 ()V
  10. #9 = Utf8 Code
  11. #10 = Utf8 LineNumberTable
  12. #11 = Utf8 LocalVariableTable
  13. #12 = Utf8 this
  14. #13 = Utf8 Lcom/yangll/jvm/bytecode/Test01;
  15. #14 = Utf8 getA
  16. #15 = Utf8 ()I
  17. #16 = Utf8 setA
  18. #17 = Utf8 (I)V
  19. #18 = Utf8 SourceFile
  20. #19 = Utf8 Test01.java
  21. #20 = NameAndType #7:#8 // "<init>":()V
  22. #21 = NameAndType #5:#6 // a:I
  23. #22 = Utf8 com/yangll/jvm/bytecode/Test01
  24. #23 = Utf8 java/lang/Object

CA FE BA BE 00 00 00 34 00 18 **0A 00 04 00 14 09 00 03 00 15 07 00 16 07 00 17 01 00 01 61 01 00 01 49 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 20 4C 63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31 3B** 01 00 04 67 65 74 41 01 00 03 28 29 49 **01 00 04 73 65 74 41** 01 00 04 28 49 29 56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 **01 00 0B 54 65 73 74 30 31 2E 6A 61 76 61** 0C 00 07 00 08 0C 00 05 00 06 **01 00 1E 63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74** 00 21 00 03 00 04 00 00 00 01 00 02 00 05 00 06 00 00 00 03 00 01 00 07 00 08 00 01 00 09 00 00 00 38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 03 00 04 00 05 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 09 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 10 00 11 00 01 00 09 00 00 00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 0D 00 05 00 0E 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D 00 00 00 00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00 **

第1项

0x0A CONSTANT_Methodref_info
0x0004 表示方法所在的类,也就是java/lang/Object#4,#23
0x0014 表示方法方法描述符,也就是20,表示"<init>":()V#20

第2项

0x09 CONSTANT_Fieldref_info
0x0003 表示字段所在类或方法,也就是 com/yangll/jvm/bytecode/Test01#3,#22
0x0015 表示字段描述符CONSTANT_NameAndType_info 的索引,也就是 a:I(#21,#5,#6)

第3项

0x07 CONSTANT_Class_info
0x0016 指向全限定名称常量项的索引,也就是 com/yangll/jvm/bytecode/Test01

第4项

0x07 CONSTANT_Class_info
0x0017 指向全限定名称常量项的索引,也就是 java/lang/Object

第5项

0x01 CONSTANT_utf8_info
0x0001 (61
a

第6项

0x01 CONSTANT_utf8_info
0x0001 (49
I

第7项

0x01 CONSTANT_utf8_info
0x0006(3C 69 6E 69 74 3E

第8项

0x01 CONSTANT_utf8_info
0x0003(28 29 56
()V
**

第9项

0x01 CONSTANT_utf8_info
0x0004(43 6F64 65
Code
**

第10项

0x01 CONSTANT_utf8_info
0x000F(4C 69 6E 65 4E 75 6D 62 65 7254 61 62 6C 65
LineNumberTable
**

第11项

0x01 CONSTANT_utf8_info
0x0012(4C 6F 63 61 6C 56 61 72 69 61 62 6C 65 54 61 62 6C 65
LocalVariableTable
**

第12项

0x01 CONSTANT_utf8_info
0x0004(74 68 69 73
this
**

第13项

0x01 CONSTANT_utf8_info
0x0020(4C 63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 3B
Lcom/yangll/jvm/bytecode/Test0;
**

第14项

0x01 CONSTANT_utf8_info
0x0004(67 65 74 41
getA
**

第15项

0x01 CONSTANT_utf8_info
0x0003(28 29 49
()I
**

第16项

0x01 CONSTANT_utf8_info
0x0004(73 65 74 41
setA
**

第17项

0x01 CONSTANT_utf8_info
0x0004(28 49 29 56
(I)V
**

第18项

0x01 CONSTANT_utf8_info
0x000A(53 6F 75 72 63 65 46 69 6C 65
SourceFile
**

第19项

0x01 CONSTANT_utf8_info
0x000B(54 65 73 74 30 31 2E 6A 61 76 61
Test01.java
**

第20项

0x0C CONSTANT_NameAndType_info
0x0007 字段或方法名称常量项索引(#7)
0x0008 字段或方法描述符常量项索引(#8) ()V
**

第21项

0x0C CONSTANT_NameAndType_info
0x0005 字段或方法名称常量项索引(#7) a
0x0006 字段或方法描述符常量项索引(#8) I
**

第22项

0x01 CONSTANT_utf8_info
0x001E(63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31
com/yangll/jvm/bytecode/Test01
**

第23项

0x01 CONSTANT_utf8_info
0x0010(6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
java/lang/Object

以上 CONSTANT_utf8_info 结果都通过[16进制转ASCII字符串](https://coding.tools/cn/hex-to-ascii)求证,结果与反编译结果一致。

**

访问标识符(u2)(1)

该 class 表示的类或文件的访问标识。JVM中声明了具体的访问表示机器含义。具体见下表:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public类型
ACC_FINAL 0x0010 是否声明为final类型,只有类可设置
ACC_SUPER 0x0020 是否允许使用invokespecial字节码指令的新语义,invokespecial指令的语义在JDK 1.0.2发生过改变,为了区别这条指令使用哪种语义,JDK1.0.2之后编译出来的类的这个标志都必须为真
ACC_INTERAFACE 0x0200 是否是一个接口
ACC_ABSTRACT 0x0400 是否为abstract类型,对于接口或抽象类来说,此标志位为真,其余为false
ACC_SYNTHETIC 0x1000 标识这个类并非由用户代码产生
ACC_ANNOTATION 0x2000 标识这是一个注解
ACC_ENUM 0x4000 标识这是一个枚举

继续分析上面的字节文件:

CA FE BA BE 00 00 00 34 00 18 0A 00 04 00 14 09 00 03 00 15 07 00 16 07 00 17 01 00 01 61 01 00 01 49 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 20 4C 63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31 3B 01 00 04 67 65 74 41 01 00 03 28 29 49 01 00 04 73 65 74 41 01 00 04 28 49 29 56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0B 54 65 73 74 30 31 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 1E 63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31 01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 **00 21 00 03 00 04 00 00 00 01** 00 02 00 05 00 06 00 00 00 03 00 01 00 07 00 08 00 01 00 09 00 00 00 38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 03 00 04 00 05 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 09 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 10 00 11 00 01 00 09 00 00 00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 0D 00 05 00 0E 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D 00 00 00 00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00** **

访问标识:0x0021,即 ACC_SUPERACC_PUBLIC,对比我们的 Java 源文件,确实我们的 Java 代码只有 public 访问标识符和访问了父类,故而访问标识符为 0x0021

类索引(u2)(1)

类索引:0x0003,指向一个 CONSTANT_Class_info 常量,表示这个类的全限定名。查看第3个常量得到 com/yangll/jvm/bytecode/Test01

父类索引(u2)(1)

类索引:0x0004,指向一个 CONSTANT_Class_info 常量,表示这个父类的全限定名。查看第4个常量得到 java/lang/Object

接口数量(u2)(1)

类索引:0x0000,表示该类没有实现任何接口

接口(u2)(interfecs_count)

字段数量(u2)(1)

0x0001,表示该类只有一个成员属性。其实根据 Java源码我们也可以知道,确实只有一个成员属性 a

字段(field_info)(fields_count)

字段(field_info)用于描述类或接口中声明的变量,包括类变量(static)和实例变量。一个 field 包含的信息有:

  • 字段的作用域(private、public、protected)
  • 是否是类变量(static)
  • 可变性(final)
  • 并发可见性(volatile)
  • 可否被序列化(transient)
  • 字段数据类型(基本类型、对象、数组)
  • 字段名称

字段访问标志如下表:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public
ACC_PRIVATE 0x0002 是否为private
ACC_PROTECTED 0x0004 是否为protected
ACC_STATIC 0x0008 是否为static
ACC_FINAL 0x0010 是否声明为final
ACC_VOLATILE 0x0040 是否为volatile
ACC_TRANSIENT 0x0080 是否为tansient
ACC_SYNTHETIC 0x1000 标识字段是否由编译器自动产生
ACC_ENUM 0x4000 是否为enum

由于字段修饰符可以使用标志位来表示,但是字段名称等只能使用CONSTANT_Utf8_info 来表示。因为 field 有自己的表示结构:

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

CA FE BA BE 00 00 00 34 00 18 0A 00 04 00 14 09 00 03 00 15 07 00 16 07 00 17 01 00 01 61 01 00 01 49 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 20 4C 63 6F 6D 2F 79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31 3B 01 00 04 67 65 74 41 01 00 03 28 29 49 01 00 04 73 65 74 41 01 00 04 28 49 29 56 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 00 0B 54 65 73 74 30 31 2E 6A 61 76 61 0C 00 07 00 08 0C 00 05 00 06 01 00 1E 63 6F 6D 2F

79 61 6E 67 6C 6C 2F 6A 76 6D 2F 62 79 74 65 63 6F 64 65 2F 54 65 73 74 30 31

01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 00 21 00 03 00 04 00

00 00 01 00 02 00 05 00 06 00 00 00 03 00 01 00 07 00 08 00 01 00 09 00 00 00

38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00

00 00 0A 00 02 00 00 00 03 00 04 00 05 00 0B 00 00 00 0C 00 01 00 00 00 0A 00

0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00

05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 09 00 0B 00 00

00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 10 00 11 00 01 00 09 00 00

00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A

00 02 00 00 00 0D 00 05 00 0E 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D

00 00 00 00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00

0x0002,字段访问标识符(access_flags),表示 private
0x0005, 字段简单名称(name_index),指向常量池引用,取值为 a
0x0006, 字段描述符(descriptor_index),指向常量池引用,取值为 I
0x0000, 字段属性属性(attributes_count),取值为0
属性表,无

方法数量(u2)(1)

0x003,表示该类有3个方法。分别为构造方法getA()setA()

方法(method_info)(methods_count)

方法访问标志如下表:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public
ACC_PRIVATE 0x0002 是否为private
ACC_PROTECTED 0x0004 是否为protected
ACC_STATIC 0x0008 是否为static
ACC_FINAL 0x0010 是否声明为final
ACC_SYNCHRONIZED 0x0020 是否为synchronized
ACC_BRIDGE 0x0040 是否为编译器产生的桥接方法
ACC_VARARGS 0x0080 是否接收不定参数
ACC_NATIVE 0x0100 是否为native
ACC_ABSTRACT 0x0200 是否为abstract
ACC_STRICTFP 0x0400 是否为strictfp
ACC_SYNTHETIC 0x1000 标识字段是否由编译器自动产生

方法的描述与字段的描述基本一致,因而有着一样的储存结构:

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

00 03 00 01 00 07 00 08 00 01 00 09 00 00 00

38 00 02 00 01 00 00 00 0A 2A B7 00 01 2A 04 B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 03 00 04 00 05 00 0B 00 00 00 0C 00 01 00 00 00 0A 00 0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00 05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 09 00 0B 00 00 00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 10 00 11 00 01 00 09 00 00 00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A 00 02 00 00 00 0D 00 05 00 0E 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D 00 00 00 00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00

0x0001,方法访问标识符(access_flags),表示 public
0x0007, 方法简单名称(name_index),指向常量池引用,取值为 <init>
0x0008, 方法描述符(descriptor_index),指向常量池引用,取值为 ()V
0x0001, 方法属性属性(attributes_count),取值为1

Code属性表结构具体参考Code属性表存储结构
0x0009 ,指向属性名称索引,是一个固定值 Code.。验证 常量池第9项
0x00000038,属性长度,即56
0x0002,操作数栈最大深度,即2
0x0001,局部变量表所需的存储空间
0x0000000A,字节码长度,即 10.
0x2AB700012A04B50002B1,源代码对应的具体字节码
将上面反编译结果拿下来,方便做一个对比:

  1. {
  2. public com.yangll.jvm.bytecode.Test01();
  3. descriptor: ()V
  4. flags: ACC_PUBLIC
  5. Code:
  6. stack=2, locals=1, args_size=1
  7. 0: aload_0
  8. 1: invokespecial #1 // Method java/lang/Object."<init>":()V
  9. 4: aload_0
  10. 5: iconst_1
  11. 6: putfield #2 // Field a:I
  12. 9: return
  13. LineNumberTable:
  14. line 3: 0
  15. line 5: 4
  16. LocalVariableTable:
  17. Start Length Slot Name Signature
  18. 0 10 0 this Lcom/yangll/jvm/bytecode/Test01;
  19. }

invokespecial

调用实例方法,父类构造方法,私有方法以及实例化方法 格式: invokespecial indexbyte1 indexbyte2

2A B7 00 01 2A 04 B5 00 02 B1

0x2A — aload_0,将第一个引用类型本地变量推送至栈顶(this) 0xB7 — invokespecial,调用超类构造函数,实例初始化方法,私有方法。很明显这里是调用父类构造函数 0x0001 表示invokespecial指令的参数值,指向常量池常量项的 CONSTANT_Methodref_info 项,表示 调用的具体方法。具体值为:java/lang/Object.”“:()V(方法名称、方法描述符、方法所属类) 这表示调用 this(栈顶)的super方法(父类构造方法)

0x2A — aload_0,将第一个引用类型本地变量推送至栈顶(this) 0x04 — iconst_1,将 int 类型的1推送至栈顶 0xB5 — putfield,为指定的类的实例域赋值,也就是将 1 赋值给变量 a 0x0002,指向 CONSTANT_Field_info 常量项,表示一个field字段。取值 为com/yangll/jvm/bytecode/Test01.a:I。 0x2A04B50002 表示将常量1(iconst_1 )赋值给 对象 this(aload_0 )的成员变量 a(0x0002)
0xB1 — return,从当前方法返回

经过分析,有16进制字节流反推Java执行流程与反编译得到的结果一致。当然,这是废话,肯定是一致的,要是反推得到的指令不一致,那就证明你推导错误。需要注意的是,有些助记符是需要参数的,也是需要目标的。例如:
putfield就需要两个参数 objectref 和 value,其中,objectref表示字段在的对象this,value表示将要赋给字段的具体值;putfield指令的后两个字节表示目标,也就是赋值操作具体是赋给this中的哪一个属性?这两个字节指向一个CONSTANT_Field_info 常量项,表示的是将要进行赋值的字段,这里是 a

具体助记符信息参考 **Java虚拟机规范**

继续分析:**

00 00 00 02 00 0A 00

00 00 0A 00 02 00 00 00 03 00 04 00 05 00 0B 00 00 00 0C 00 01 00 00 00 0A 00

0C 00 0D 00 00 00 01 00 0E 00 0F 00 01 00 09 00 00 00 2F 00 01 00 01 00 00 00

05 2A B4 00 02 AC 00 00 00 02 00 0A 00 00 00 06 00 01 00 00 00 09 00 0B 00 00

00 0C 00 01 00 00 00 05 00 0C 00 0D 00 00 00 01 00 10 00 11 00 01 00 09 00 00

00 3E 00 02 00 02 00 00 00 06 2A 1B B5 00 02 B1 00 00 00 02 00 0A 00 00 00 0A

00 02 00 00 00 0D 00 05 00 0E 00 0B 00 00 00 16 00 02 00 00 00 06 00 0C 00 0D

00 00 00 00 00 06 00 05 00 06 00 01 00 01 00 12 00 00 00 02 00

0x0000,方法异常表长度,很明显构造方法是不存在异常的。
异常属性信息,无
0x0002,属性数量

0x000A,属性名称索引,指向第10个常量项,这里取值为LineNumberTable。关于 LineNumberTableLineNumberTable属性表存储结构
0x0000000A, 属性长度,取值为10
0x0002,line_number_table_length(行号表长度),取值为2
0x0000,字节码行号,也就是指字节码相对于方法体开始的偏移量。对应的值为 aload_0
0x0003,Java源码行号,对应的值为 public class Test01
0x0004,字节码偏移量,对应的取值为 putfield
0x0005,Java 源码行号,对应 private int a = 1;

0x000B,属性名称索引,指向第11个常量项,取值为 LocalVariableTable
0x0000000C,属性长度,取值12
0x0001,local_variable_table_length(局部变量表长度),取值为1
0x0000,局部变量生命周期偏移量,取值为0
0x000A,局部变量作用范围长度,取值为10
0x000C,局部变量名称在常量池的索引,取值为 this
0x000D,局部变量的描述符,取值为Lcom/yangll/jvm/bytecode/Test01;
0x0000,局部变量在栈帧局局部变量表中Slot的位置,为0,表示第一个。

至此,关于构造方法已经全部分析完毕了。字节流与反编译结构两者印证,定能让你收获不少。**

属性数量(u2)(1)

属性数量,

属性(attribute_info)(attributes_count)

属性是用来描述某些场景专用的信息的。除了 JVM 规范定义的属性,自定义实现的编译器也可以自定义属性,以供在运行时使用。

JVM预定的属性

属性名称 使用位置 含义
Code 方法表 Java代码编译成的字节码指令
ConstantValue 字段表 final关键字定义的常量值
Deprecated 类、字段表、方法表 被声明为deprecated 的方法和字段
Exceptions 方法表 方法抛出的异常
EnclosingMethod 类文件 进当一个类为局部类和匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法
InnerClass 类文件 内部类列表
LineNumberTable Code属性 Java源码的行号与字节码指令的对应关系
LocalVariableTable Code属性 方法的局部变量描述
StackMapTable Code属性 JDK1.6中新增的属性,供新的类型检查校验器(Type Checker)检查和处理目标方法的局部变量和操作数栈所需要的类型是否匹配
Signature 类、字段表、方法表 JDK1.5中新增的属性,用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法和成员的泛型签名如果包含了类型变量(Type Variable)或参数化类型(Parameterized Type),则 Signature 属性会为他记录泛型签名信息。由于Java的泛型使用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型的泛型信息
SourceFile 类文件 记录源文件名称
SourceDebugExtension 类文件 JDK1.6中新增的属性,SourceDebugExtension属性用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45为这些非Java语言编写,但却需要编译成字节码并运行在JVM上的程序提供了一个进行调试的标准机制。
Synthetic 类、字段表、方法表 标识方法或字段由编译器自动生成
LocalVariableTypeTable JDK1.5中新增的属性,使用特征签名替代描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加
RuntimeVisibleAnnotations 类、字段表、方法表 JDK1.5中新增的属性,为动态注解提供支持,用于指明哪些属性试运行是可见的(也就是可通过反射进行调用的)
RuntimeInvisibleAnnotations 类、字段表、方法表 JDK1.5中新增的属性,为动态注解提供支持,用于指明哪些属性试运行是不可见的(也就是不可以通过反射进行调用的)
RuntimeVisibleParameterAnnotations 方法表 JDK1.5中新增的属性,作用与RuntimeVisibleAnnotations一样,只不过作用对象是方法参数
RuntimeInvisibleParameterAnnotations 方法表 JDK1.5中新增的属性,作用与RuntimeInvisibleAnnotations一样,只不过作用对象是方法参数
AnnotationDefault 方法表 JDK1.5中新增的属性,用于记录注解类元素的默认值
BootstrapMethods 类文件 JDK1.7中新增的属性,用于保存invokedynamic指令作用的引导方法限定符

attribute_info 的存储结构:

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u1 attribute_info attributes_length
  • attribute_name_index 指向常量池索引,表示该属性的名称
  • attribute_length 表示属性的长度

Code属性表存储结构

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 max_stacks 1
u2 max_locals 1
u4 code_length 1
u1 code code_length
u2 exception_table_length 1
excetion_info exception_table exception_table_length
u2 attributes_count 1
attributes_info attributes exception_table_length

attribute_name_index指向CONSTANT_utf8_info常量项索引,常量值固定为Code attribute_length 表示属性的长度 max_stacks表示操作数栈的最大深度 max_locals表示局部变量表所需的存储空间,基本单位是 Slot。Slot是虚拟机为局部变量表分配内存所使用的最小单位。对于byte,char,float,int,short,boolean,和 returnAddress 等长度不超过32bit的数据类型,每个局部变量占用一个 Slot,对于 long,double这两种64bit的数据类型来说,每个局部变量占两个Slot。 code_length表示字节码长度。注意,这里虽然长度为4个字节,但实际上JVM中已经规定方法的长度不得超过2个字节,否则会报编译错误。 code用于存储字节码指令的一系列字节流。每一个指令就是一个 u1 类型,每读到一个字节码时,就会根据> JVM字节码指令集查找到该字节码对应的指令,并且知道该指令后面是否需要携带参数,以及参数应该如何理解。 exception-table_length表示异常的长度 exception_table表示异常表。 attributes_count:Code属性表内嵌的属性表数量。可以内嵌的属性表见 > JVM预定属性 attributes : 内嵌属性

异常属性表存储结构

类型 名称 数量
u2 start_pc 1
u2 end_pc 1
u2 handler_pc 1
u2 catch_type 1

如果在第start_pc行(这里的行表示字节码相对于方法体开始的偏移量)与第end_pc行之间(不包含end_pc行)发生了catch_type或者其子类的异常(catch_type指向一个CONSTANT_Class_info型常量的索引),则转到第handler_pc行继续处理。当catch_type的值为0时,表示任意异常情况都需要转向handler_pc处进行处理。

Exceptions属性表存储结构

列出方法中所有可能抛出的授检查异常。也就是方法描述时在throw关键字后面列举的异常。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_exceptions 1
u2 exception_index_table number_of_exceptions

number_of_exceptions 表示方法可能抛出的受检查异常种类 exception_index_table 表示一种异常,指向一个CONSTANT_Class_info常量项

LineNumberTable属性表存储结构

用于描述Java源代码与字节码行号(字节码偏移量)之间的对应关系。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 line_number_table_length 1
line_number_info line_number_table line_number_table_length

line_number_table_length 表示行号表长度 line_number_table 是一个数量为line_number_table_length ,类型为 line_number_info 的集合

line_number_info存储结构

类型 名称 数量
u2 start_pc 1
u4 line_number 1

start_pc 表示字节码行号 line_number 表示Java源代码行号

LocalVariableTable属性表存储结构

用于描述栈帧中局部变量表的变量与Java源码中定义个变量之间的关系。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 local_variable_table_length 1
local_variable_info local_variable_table local_variable_table_length

local_variable_info存储结构如下:

类型 名称 数量
u2 start_pc 1
u2 length 1
u2 name_index 1
u2 descriptor_index 1
u2 index 1

start_pc 表示局部变量的生命周期开始的字节码偏移量 length 表示局部变量的作用域覆盖长度 name_index 表示局部变量的名称,指向CONSTANT_Utf8_info descriptor_index 表示局部变量的描述符,指向CONSTANT_Utf8_info index 表示局部变量在栈帧局部变量表中的Slot的位置。当这个变量类型是64位的long或double类型时,占用的Slot事index以及index+1两个

SourceFile属性表存储结构

用于记录生成这个Class文件的Java源文件的名称。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 sourcefile_index 1

sourcefile_index 指向 CONSTANT_Utf8_info 型常量的索引,常来值表示源文件的文件名称

ConstantValue 属性表存储结构

用于通知JVM自动为静态变量赋值。静态变量有两种赋值的选择:

  • 中赋值
  • 使用ConstantVlaue属性

在目前Sun Javac 编译器中,如果同时使用static 和 final 关键字修饰一个变量,且这个变量是基本数据类型或String类型,则采用 ConstantValue 属性进行初始化,否则将在 中进行赋值。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 constantvalue_index 1

constantvalue_index 表示常量池中一个字面常量的引用,根据字段类型的不同,字面量可以是 CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_Integer_info、CONSTANT_String_info中的一种。

InnerClass属性表的存储结构

用于记录内部类与宿主类之间的关联。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_classes 1
inner_class_info inner_class unmber_of_classes

number_of_classes 表示记录的内部类的数量 inner_class_info 表示内部类信息的描述

inner_class_info存储结构

类型 名称 数量
u2 inner_class_info_index 1
u2 ooter_class_info_index 1
u2 inner_class_index 1
u2 inner_class_access_flag 1

inner_class_info_index 指向常量池 CONSTANT_Class_info 型常量项索引,表示内部类的符号引用 outer_class_info_index 指向常量池 CONSTANT_Class_info 型常量项索引,表示宿主类的符号引用 inner_name_index 指向常量项中 CONSTANT_Utf8_info 型常量项索引,代表这个内部类的名称,如果为匿名内部类,则这个值位0 inner_class_access_flag 表示内部类的访问标志。取值如下:

标志名称 标志值 含义
ACC_PUBLIC 0x0001 是否为public
ACC_PRIVATE 0x0002 是否为private
ACC_PROTECTED 0x0004 是否为protected
ACC_STATIC 0x0008 是否为static
ACC_FINAL 0x0010 是否声明为final
ACC_INTERFACE 0x0020 是否为接口
ACC_ABSTRACT 0x0400 是否为abstract
ACC_SYNTHETIC 0x1000 标识是否由编译器自动产生
ACC_ANNOTATION 0x2000 标识是否是注解
ACC_ENUM 0x4000 是否为enum

Deprecated以及Synthetic属性表存储结构

两个都是bool类型属性值,只存在有没有,不存在属性值。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1

attribute_length 的取值必须为 0x00000000,因为不存在任何属性值。

StackMapTable属性表存储结构

一个复杂的变长结构,用于Code属性的属性表。这个属性会在JVM类加载的字节码校验阶段被新的类型检查校验器(Type Checker)使用,目的是图带一千比较消耗性能的基于数据类分析的类型推导校验器。

StackMapTable 中包含了零至多个栈映射帧,每个栈映射帧都显式或隐式地代表一个字节码偏移量,用于表示该执行到该字节码时局部变量表和操作数栈的验证类型。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 number_of_entries 1
stack_map_frame stack_map_frame_entries nember_of_entries

一个Code属性表最多只能有一个StackMapTable属性,否则将抛出ClassFormatException

Signature属性表存储结构

使用于类、字段表和方法表中。用于记录它们的泛型签名信息。因为Java使用擦除法实现发型,即在字节码Code属性中,泛型信息编译(类型变量、参数化类型)之后都通通被擦擦除掉。

好处:实现简单,运行期也能够节省一些类型所占用的内存空间 缺点:运行期无法将泛型类型和用户自定义的普通类型同等对待,例如无法通过反射获取泛型信息。

Signature 就是用来弥补这个缺点的,现在我们能够通过反射获取类型信息,主要信息都是来源于这个属性。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 signature_index 1

signature_index 指向一个CONSTANT_Utf8_info 结构,表示类签名、方法类型签名、或字段类型签名

BootstrapMethods属性表存储结构

用于保存invokedynamic指令引用的引导方法限定符。在《Java虚拟机规范(Java SE 7版)》中规定,如果某个类文件 结构中的常量池中曾经出现过 CONSTANT_InvokeDynamic_info类型的常量,那么这个类文件的属性表中必须存在一个明确的BootstrapMethods 属性。

类型 名称 数量
u2 attribute_name_index 1
u4 attribute_length 1
u2 num_bootstrap_methods 1
bootstrap_method bootstrap_methods unm_bootstrap_methods

bootstrap_method存储结构

类型 名称 数量
u2 bootstrap_methpod_ref 1
u2 num_bootstrap_arguments 1
u2 bootstrap_arguments num_bootstrap_arguments

num_bootstrap_arguments 指定了 bootstrap_method[] 数组中的引导方法限定符的数量,bootstrap_method[] 数组中的每个成员包含一个指向常量池的CONSTANT_MethodHandle 结构的索引值,它表示一个引导方法,还包含这个引导方法静态参数的序列(可能为空)。bootstrap_method[] 数组中的每个成员必须包含以下3项内容:

  • bootstrap_methpod_ref 指向一个 CONSTANT_MethodHandle_info型常量项索引
  • num_bootstrap_arguments 指定了 bootstrap_method[] 数组成员的数量
  • bootstrap_arguments 包含多个引导方法参数,指向的常量池类型必须是以下几种:
  1. CONSTANT_String_info
  2. CONSTANT_Class_info
  3. CONSTANT_Integer_info
  4. CONSTANT_Float_info
  5. CONSTANT_Long_info
  6. CONSTANT_Double_info
  7. CONSTANT_MethodHandle_info
  8. CONSTANT_MethodType_info