无关性的基石
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 |
初识字节码
首先看一个简单的程序:
package com.yangll.jvm.bytecode;public class Test01 {private int a = 1;public int getA() {return a;}public void setA(int a) {this.a = a;}}
反编译生成的class文件:
javap -c D:\project\java\build\classes\java\main\com\yangll\jvm\bytecode\Test01.class
Compiled from "Test01.java"public class com.yangll.jvm.bytecode.Test01 {public com.yangll.jvm.bytecode.Test01();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: iconst_16: putfield #2 // Field a:I9: returnpublic int getA();Code:0: aload_01: getfield #2 // Field a:I4: ireturnpublic void setA(int);Code:0: aload_01: iload_12: putfield #2 // Field a:I5: return}
public com.yangll.jvm.bytecode.Test01();Code:0: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: iconst_16: putfield #2 // Field a:I9: 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
Classfile /D:/project/java/build/classes/java/main/com/yangll/jvm/bytecode/Test01.classLast modified 2020-4-24; size 490 bytesMD5 checksum b767989cc41189e99e1255dc313c5d0cCompiled from "Test01.java"public class com.yangll.jvm.bytecode.Test01minor version: 0major version: 52flags: ACC_PUBLIC, ACC_SUPERConstant pool:#1 = Methodref #4.#20 // java/lang/Object."<init>":()V#2 = Fieldref #3.#21 // com/yangll/jvm/bytecode/Test01.a:I#3 = Class #22 // com/yangll/jvm/bytecode/Test01#4 = Class #23 // java/lang/Object#5 = Utf8 a#6 = Utf8 I#7 = Utf8 <init>#8 = Utf8 ()V#9 = Utf8 Code#10 = Utf8 LineNumberTable#11 = Utf8 LocalVariableTable#12 = Utf8 this#13 = Utf8 Lcom/yangll/jvm/bytecode/Test01;#14 = Utf8 getA#15 = Utf8 ()I#16 = Utf8 setA#17 = Utf8 (I)V#18 = Utf8 SourceFile#19 = Utf8 Test01.java#20 = NameAndType #7:#8 // "<init>":()V#21 = NameAndType #5:#6 // a:I#22 = Utf8 com/yangll/jvm/bytecode/Test01#23 = Utf8 java/lang/Object{public com.yangll.jvm.bytecode.Test01();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: iconst_16: putfield #2 // Field a:I9: returnLineNumberTable:line 3: 0line 5: 4LocalVariableTable:Start Length Slot Name Signature0 10 0 this Lcom/yangll/jvm/bytecode/Test01;public int getA();descriptor: ()Iflags: ACC_PUBLICCode:stack=1, locals=1, args_size=10: aload_01: getfield #2 // Field a:I4: ireturnLineNumberTable:line 9: 0LocalVariableTable:Start Length Slot Name Signature0 5 0 this Lcom/yangll/jvm/bytecode/Test01;public void setA(int);descriptor: (I)Vflags: ACC_PUBLICCode:stack=2, locals=2, args_size=20: aload_01: iload_12: putfield #2 // Field a:I5: returnLineNumberTable:line 13: 0line 14: 5LocalVariableTable:Start Length Slot Name Signature0 6 0 this Lcom/yangll/jvm/bytecode/Test01;0 6 1 a I}SourceFile: "Test01.java"
使用 javap -verbose 命令分析一个字节码文件时,将会分析该字节码文件的魔数,版本号,常量池,类信息,类的构造方法,类的方法信息,类变量和成员变量的信息
前面说过,class文件是一个字节流文件,我们通过反编译看到的都是比较直观的信息,那怎样看到对应的字节流呢?Windows中,可以使用 WinHex 软件打开class文件,即可看到对应的字节流。使用 WinHex 打开得到如下所示:
下载 winHex 软件,打开class文件可以得到:
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类定义的很多信息都是由常量池来维护和描述的。常量池中主要存放两大类常量:字面量和符号引用。
- 字面量:字符串文本、final变量
- 符号引用
- 类和接口的全限定名
- 字段的名称和描述符
- 方法的名称和描述符
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; |
Constant pool:#1 = Methodref #4.#20 // java/lang/Object."<init>":()V#2 = Fieldref #3.#21 // com/yangll/jvm/bytecode/Test01.a:I#3 = Class #22 // com/yangll/jvm/bytecode/Test01#4 = Class #23 // java/lang/Object#5 = Utf8 a#6 = Utf8 I#7 = Utf8 <init>#8 = Utf8 ()V#9 = Utf8 Code#10 = Utf8 LineNumberTable#11 = Utf8 LocalVariableTable#12 = Utf8 this#13 = Utf8 Lcom/yangll/jvm/bytecode/Test01;#14 = Utf8 getA#15 = Utf8 ()I#16 = Utf8 setA#17 = Utf8 (I)V#18 = Utf8 SourceFile#19 = Utf8 Test01.java#20 = NameAndType #7:#8 // "<init>":()V#21 = NameAndType #5:#6 // a:I#22 = Utf8 com/yangll/jvm/bytecode/Test01#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_SUPER和 ACC_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,源代码对应的具体字节码
将上面反编译结果拿下来,方便做一个对比:
{public com.yangll.jvm.bytecode.Test01();descriptor: ()Vflags: ACC_PUBLICCode:stack=2, locals=1, args_size=10: aload_01: invokespecial #1 // Method java/lang/Object."<init>":()V4: aload_05: iconst_16: putfield #2 // Field a:I9: returnLineNumberTable:line 3: 0line 5: 4LocalVariableTable:Start Length Slot Name Signature0 10 0 this Lcom/yangll/jvm/bytecode/Test01;}
调用实例方法,父类构造方法,私有方法以及实例化方法 格式: 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。关于 LineNumberTable见 LineNumberTable属性表存储结构
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 包含多个引导方法参数,指向的常量池类型必须是以下几种:
- CONSTANT_String_info
- CONSTANT_Class_info
- CONSTANT_Integer_info
- CONSTANT_Float_info
- CONSTANT_Long_info
- CONSTANT_Double_info
- CONSTANT_MethodHandle_info
- CONSTANT_MethodType_info
