实现语言无关性的基础仍然是虚拟机和字节码存储格式。Java的规范拆分成了Java语言规范《The Java Language Specification》及Java虚拟机规范《The Java Virtual Machine Specification》,Java虚拟机不和包括Java在内的任何语言绑定,它只与“Class文件”这种特定的二进制文件格式所关联,Class文件中包含了Java虚拟机指令集和符号表以及若干其他辅助信息。Java虚拟机发展到JDK 1.7~1.8的时候,JSR-292基本支持了将其他语言运行于JVM之上。
1. Class文件的结构
Class文件是一组以8位字节为基础单位的二进制流,各个数据项目严格按照顺序紧凑地排列在Class文件之中,中间没有添加任何分隔符,这使得整个Class文件中存储的内容几乎全部是程序运行的必要数据,没有空隙存在。当遇到需要占用8位字节以上空间的数据项时,则会按照高位在前(Big-Endian,最高位字节在地址最低位、最低位字节在地址最高位的顺序来存储数据)的方式分割成若干个8位字节进行存储。
Class文件格式采用一种类似于C语言结构体的伪结构来存储数据,这种伪结构中只有两种数据类型:无符号数和表。
- 无符号数属于基本的数据类型,以
u1
、u2
、u4
、u8
来分别代表1个字节、2个字节、4个字节和8个字节的无符号数,无符号数可以用来描述数字、索引引用、数量值或者按照UTF-8编码构成字符串值。 - 表是由多个无符号数或者其他表作为数据项构成的复合数据类型,所有表都习惯性地以
_info
结尾。表用于描述有层次关系的复合结构的数据,整个Class文件本质上就是一张表,它由下表1 - 字节码结构表所示的数据项构成:
类型 | 名称 | 数量 | 含义 | 长度 |
---|---|---|---|---|
u4 | magic | 1 | 魔数 | 4 |
u2 | minor_version | 1 | 次版本 | 2 |
u2 | major_version | 1 | 主版本 | 2 |
u2 | constant_pool_count | 1 | 常量池计数器 | 2 |
cp_info | constant_pool | constant_pool_count - 1 | 常量表 | N |
u2 | access_flags | 1 | 访问标志 | 2 |
u2 | this_class | 1 | 类索引 | 2 |
u2 | super_class | 1 | 父类索引 | 2 |
u2 | interfaces_count | 1 | 接口计数器 | 2 |
u2 | interfaces | interfaces_count | 接口索引集合 | 2 |
u2 | fields_count | 1 | 字段计数器 | 2 |
field_info | fields | fields_count | 字段表 | N |
u2 | methods_count | 1 | 方法计数器 | 2 |
method_info | methods | methods_count | 方法表 | N |
u2 | attribute_count | 1 | 附加属性计数器 | 2 |
attribute_info | attributes | attributes_count | 附加属性表 | N |
无论是无符号数还是表,当需要描述同一类型但数量不定的多个数据时,会使用一个前置的容量计数器加若干个连续的数据项的形式,这时称这一系列连续的某一类型的数据为某一类型的集合。
由于Class文件内容没有任何分隔符号,所以上表中的数据项,无论是顺序还是数量,甚至于数据存储的字节序(Byte Ordering,Class文件中字节序为Big-Endian)细节,都是被严格限定的,哪个字节代表什么含义,长度是多少,先后顺序如何,都不允许改变。
我们以下面的Java源文件,使用JDK 1.7.0_07版本进行编译:
- import java.io.Serializable;
- public class ClassFileTest<T> implements Serializable, Cloneable {
- T t;
- public static final int i = 1;
- public static void main(String[] args) {
- String j = "2";
- }
- @Deprecated
- public double divide(int number1, int number2) {
- double result;
- try {
- result = number1 * 1.0 / number2;
- return result;
- } catch (ArithmeticException e) {
- e.printStackTrace();
- return 0;
- } finally {
- result = 0;
- }
- }
- class InnerClass {
- private int add(int number1, int number2) throws IllegalArgumentException {
- if (number1 <= 0 || number2 <= 0) {
- throw new IllegalArgumentException("number1 and number2 should be > 0");
- }
- return number1 + number2 + i;
- }
- }
- }
编译得到的字节码文件有两个,一个是ClassFileTest.class文件,代表ClassFileTest类,它的字节码内容如下:
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33 00 30 0A 00 06 00 25 08 J~:>...3.0....%.
- 00000010: 00 26 07 00 27 0A 00 03 00 28 07 00 29 07 00 2A .&..'....(..)..*
- 00000020: 07 00 2B 07 00 2C 07 00 2D 01 00 0A 49 6E 6E 65 ..+..,..-...Inne
- 00000030: 72 43 6C 61 73 73 01 00 0C 49 6E 6E 65 72 43 6C rClass...InnerCl
- 00000040: 61 73 73 65 73 01 00 01 74 01 00 12 4C 6A 61 76 asses...t...Ljav
- 00000050: 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74 3B 01 00 a/lang/Object;..
- 00000060: 09 53 69 67 6E 61 74 75 72 65 01 00 03 54 54 3B .Signature...TT;
- 00000070: 01 00 01 69 01 00 01 49 01 00 0D 43 6F 6E 73 74 ...i...I...Const
- 00000080: 61 6E 74 56 61 6C 75 65 03 00 00 00 01 01 00 06 antValue........
- 00000090: 3C 69 6E 69 74 3E 01 00 03 28 29 56 01 00 04 43 <init>...()V...C
- 000000a0: 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 ode...LineNumber
- 000000b0: 54 61 62 6C 65 01 00 04 6D 61 69 6E 01 00 16 28 Table...main...(
- 000000c0: 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 [Ljava/lang/Stri
- 000000d0: 6E 67 3B 29 56 01 00 06 64 69 76 69 64 65 01 00 ng;)V...divide..
- 000000e0: 05 28 49 49 29 44 01 00 0D 53 74 61 63 6B 4D 61 .(II)D...StackMa
- 000000f0: 70 54 61 62 6C 65 07 00 27 07 00 2E 01 00 0A 44 pTable..'......D
- 00000100: 65 70 72 65 63 61 74 65 64 01 00 19 52 75 6E 74 eprecated...Runt
- 00000110: 69 6D 65 56 69 73 69 62 6C 65 41 6E 6E 6F 74 61 imeVisibleAnnota
- 00000120: 74 69 6F 6E 73 01 00 16 4C 6A 61 76 61 2F 6C 61 tions...Ljava/la
- 00000130: 6E 67 2F 44 65 70 72 65 63 61 74 65 64 3B 01 00 ng/Deprecated;..
- 00000140: 53 3C 54 3A 4C 6A 61 76 61 2F 6C 61 6E 67 2F 4F S<T:Ljava/lang/O
- 00000150: 62 6A 65 63 74 3B 3E 4C 6A 61 76 61 2F 6C 61 6E bject;>Ljava/lan
- 00000160: 67 2F 4F 62 6A 65 63 74 3B 4C 6A 61 76 61 2F 69 g/Object;Ljava/i
- 00000170: 6F 2F 53 65 72 69 61 6C 69 7A 61 62 6C 65 3B 4C o/Serializable;L
- 00000180: 6A 61 76 61 2F 6C 61 6E 67 2F 43 6C 6F 6E 65 61 java/lang/Clonea
- 00000190: 62 6C 65 3B 01 00 0A 53 6F 75 72 63 65 46 69 6C ble;...SourceFil
- 000001a0: 65 01 00 12 43 6C 61 73 73 46 69 6C 65 54 65 73 e...ClassFileTes
- 000001b0: 74 2E 6A 61 76 61 0C 00 14 00 15 01 00 01 32 01 t.java........2.
- 000001c0: 00 1D 6A 61 76 61 2F 6C 61 6E 67 2F 41 72 69 74 ..java/lang/Arit
- 000001d0: 68 6D 65 74 69 63 45 78 63 65 70 74 69 6F 6E 0C hmeticException.
- 000001e0: 00 2F 00 15 01 00 0D 43 6C 61 73 73 46 69 6C 65 ./.....ClassFile
- 000001f0: 54 65 73 74 01 00 10 6A 61 76 61 2F 6C 61 6E 67 Test...java/lang
- 00000200: 2F 4F 62 6A 65 63 74 01 00 14 6A 61 76 61 2F 69 /Object...java/i
- 00000210: 6F 2F 53 65 72 69 61 6C 69 7A 61 62 6C 65 01 00 o/Serializable..
- 00000220: 13 6A 61 76 61 2F 6C 61 6E 67 2F 43 6C 6F 6E 65 .java/lang/Clone
- 00000230: 61 62 6C 65 01 00 18 43 6C 61 73 73 46 69 6C 65 able...ClassFile
- 00000240: 54 65 73 74 24 49 6E 6E 65 72 43 6C 61 73 73 01 Test$InnerClass.
- 00000250: 00 13 6A 61 76 61 2F 6C 61 6E 67 2F 54 68 72 6F ..java/lang/Thro
- 00000260: 77 61 62 6C 65 01 00 0F 70 72 69 6E 74 53 74 61 wable...printSta
- 00000270: 63 6B 54 72 61 63 65 00 21 00 05 00 06 00 02 00 ckTrace.!.......
- 00000280: 07 00 08 00 02 00 00 00 0C 00 0D 00 01 00 0E 00 ................
- 00000290: 00 00 02 00 0F 00 19 00 10 00 11 00 01 00 12 00 ................
- 000002a0: 00 00 02 00 13 00 03 00 01 00 14 00 15 00 01 00 ................
- 000002b0: 16 00 00 00 21 00 01 00 01 00 00 00 05 2A B7 00 ....!........*7.
- 000002c0: 01 B1 00 00 00 01 00 17 00 00 00 0A 00 02 00 00 .1..............
- 000002d0: 00 03 00 04 00 19 00 09 00 18 00 19 00 01 00 16 ................
- 000002e0: 00 00 00 20 00 01 00 02 00 00 00 04 12 02 4C B1 ..............L1
- 000002f0: 00 00 00 01 00 17 00 00 00 0A 00 02 00 00 00 08 ................
- 00000300: 00 03 00 09 00 01 00 1A 00 1B 00 03 00 16 00 00 ................
- 00000310: 00 86 00 04 00 09 00 00 00 26 1B 87 0F 6B 1C 87 .........&...k..
- 00000320: 6F 4A 29 39 05 0E 4A 18 05 AF 3A 05 19 05 B6 00 oJ)9..J../:...6.
- 00000330: 04 0E 39 06 0E 4A 18 06 AF 3A 08 0E 4A 19 08 BF ..9..J../:..J..?
- 00000340: 00 04 00 00 00 0B 00 10 00 03 00 00 00 0B 00 1F ................
- 00000350: 00 00 00 10 00 1A 00 1F 00 00 00 1F 00 21 00 1F .............!..
- 00000360: 00 00 00 02 00 17 00 00 00 1E 00 07 00 00 00 0F ................
- 00000370: 00 08 00 10 00 0B 00 15 00 10 00 11 00 12 00 12 ................
- 00000380: 00 17 00 13 00 1A 00 15 00 1C 00 00 00 0A 00 02 ................
- 00000390: 50 07 00 1D 4E 07 00 1E 00 1F 00 00 00 00 00 20 P...N...........
- 000003a0: 00 00 00 06 00 01 00 21 00 00 00 03 00 0E 00 00 .......!........
- 000003b0: 00 02 00 22 00 23 00 00 00 02 00 24 00 0B 00 00 ...".#.....$....
- 000003c0: 00 0A 00 01 00 09 00 05 00 0A 00 00 ............
另一个是ClassFileTest$InnerClass.class文件,代表InnerClass类,它的字节码内容如下:
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33 00 21 09 00 06 00 14 0A J~:>...3.!......
- 00000010: 00 07 00 15 07 00 16 08 00 17 0A 00 03 00 18 07 ................
- 00000020: 00 1A 07 00 1D 01 00 06 74 68 69 73 24 30 01 00 ........this$0..
- 00000030: 0F 4C 43 6C 61 73 73 46 69 6C 65 54 65 73 74 3B .LClassFileTest;
- 00000040: 01 00 06 3C 69 6E 69 74 3E 01 00 12 28 4C 43 6C ...<init>...(LCl
- 00000050: 61 73 73 46 69 6C 65 54 65 73 74 3B 29 56 01 00 assFileTest;)V..
- 00000060: 04 43 6F 64 65 01 00 0F 4C 69 6E 65 4E 75 6D 62 .Code...LineNumb
- 00000070: 65 72 54 61 62 6C 65 01 00 03 61 64 64 01 00 05 erTable...add...
- 00000080: 28 49 49 29 49 01 00 0D 53 74 61 63 6B 4D 61 70 (II)I...StackMap
- 00000090: 54 61 62 6C 65 01 00 0A 45 78 63 65 70 74 69 6F Table...Exceptio
- 000000a0: 6E 73 01 00 0A 53 6F 75 72 63 65 46 69 6C 65 01 ns...SourceFile.
- 000000b0: 00 12 43 6C 61 73 73 46 69 6C 65 54 65 73 74 2E ..ClassFileTest.
- 000000c0: 6A 61 76 61 0C 00 08 00 09 0C 00 0A 00 1E 01 00 java............
- 000000d0: 22 6A 61 76 61 2F 6C 61 6E 67 2F 49 6C 6C 65 67 "java/lang/Illeg
- 000000e0: 61 6C 41 72 67 75 6D 65 6E 74 45 78 63 65 70 74 alArgumentExcept
- 000000f0: 69 6F 6E 01 00 21 6E 75 6D 62 65 72 31 20 61 6E ion..!number1.an
- 00000100: 64 20 6E 75 6D 62 65 72 32 20 73 68 6F 75 6C 64 d.number2.should
- 00000110: 20 62 65 20 3E 20 30 0C 00 0A 00 1F 07 00 20 01 .be.>.0.........
- 00000120: 00 18 43 6C 61 73 73 46 69 6C 65 54 65 73 74 24 ..ClassFileTest$
- 00000130: 49 6E 6E 65 72 43 6C 61 73 73 01 00 0A 49 6E 6E InnerClass...Inn
- 00000140: 65 72 43 6C 61 73 73 01 00 0C 49 6E 6E 65 72 43 erClass...InnerC
- 00000150: 6C 61 73 73 65 73 01 00 10 6A 61 76 61 2F 6C 61 lasses...java/la
- 00000160: 6E 67 2F 4F 62 6A 65 63 74 01 00 03 28 29 56 01 ng/Object...()V.
- 00000170: 00 15 28 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 ..(Ljava/lang/St
- 00000180: 72 69 6E 67 3B 29 56 01 00 0D 43 6C 61 73 73 46 ring;)V...ClassF
- 00000190: 69 6C 65 54 65 73 74 00 20 00 06 00 07 00 00 00 ileTest.........
- 000001a0: 01 10 10 00 08 00 09 00 00 00 02 00 00 00 0A 00 ................
- 000001b0: 0B 00 01 00 0C 00 00 00 22 00 02 00 02 00 00 00 ........".......
- 000001c0: 0A 2A 2B B5 00 01 2A B7 00 02 B1 00 00 00 01 00 .*+5..*7..1.....
- 000001d0: 0D 00 00 00 06 00 01 00 00 00 19 00 02 00 0E 00 ................
- 000001e0: 0F 00 02 00 0C 00 00 00 42 00 03 00 03 00 00 00 ........B.......
- 000001f0: 18 1B 9E 00 07 1C 9D 00 0D BB 00 03 59 12 04 B7 .........;..Y..7
- 00000200: 00 05 BF 1B 1C 60 04 60 AC 00 00 00 02 00 0D 00 ..?..`.`,.......
- 00000210: 00 00 0E 00 03 00 00 00 1B 00 08 00 1C 00 12 00 ................
- 00000220: 1E 00 10 00 00 00 04 00 02 08 09 00 11 00 00 00 ................
- 00000230: 04 00 01 00 03 00 02 00 12 00 00 00 02 00 13 00 ................
- 00000240: 1C 00 00 00 0A 00 01 00 06 00 19 00 1B 00 00 ...............
下面我们将分析编译后得到的字节码文件中具体信息。
2. 魔数
Class文件最开始的4个字节表示为魔数(Magic Number),用于确定文件是否为Class文件。Class文件的魔数值为0xCAFEBABE。如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000:(CA FE BA BE)00 00 00 33 00 30 0A 00 06 00 25 08 J~:>...3.0....%.
- ...
注意:第一行和第二行的Order序号信息是后期添加的,便于分析和观察的辅助数据,字节码文件中没有这部分数据。
3. 次版本号和主版本号
接着魔数之后分别是次版本号(Minor Version)和主版本号(Major Version)。第5和第6个字节是次版本号,第7和第8个字节是主版本号。之前我们进行编译测试的DecompileTest类而言,它使用的JDK版本是1.7.0_07,因此对应的主版本号应该是51,即十六进制为0x0033。如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE(00 00 00 33)00 30 0A 00 06 00 25 08 J~:>...3.0....%.
- ...
4. 常量池
接下来的第9和第10个字节,表示常量池计数器值(constant_pool_count,u2类型,可对照表1查看),从1开始计数;本例中为0x0030,十进制为48,表示常量池中有47个常量,索引值为1 ~ 47。如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33(00 30)0A 00 06 00 25 08 J~:>...3.0....%.
- ...
注:将第0项常量空出来是有特殊考虑的,这样做的目的在于满足后面某些指向常量池的索引值的数据在特定情况下需要表达“不引用任何一个常量池项目”的含义,这种情况就可以把索引值置为0来表示。Class文件结构中只有常量池的容量计数是从1开始,对于其他集合类型,包括接口索引集合、字段表集合、方法表集合等的容量计数都与一般习惯相同,是从0开始的。
常量池中主要存放两大类常量:
- 字面量(Literal):比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。
- 符号引用(Symbolic Reference):属于编译原理方面的概念,包括了下面三类常量:
- 类和接口的全限定名(Fully Qualified Name)
- 字段的名称和描述符(Descriptor)
- 方法的名称和描述符
常量池中每一项常量都是一个表,在JDK 1.7之前共有11种结构各不相同的表结构数据,在JDK 1.7中额外增加了3种:CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info。
这14种表开始的第一位是一个u1类型的标志位(tag,取值详见下表),代表当前这个常量属于哪种常量类型。这14种常量类型所代表的具体含义下表,表2 - 常量池的项目类型表:
常量 | 项目 | 类型 | 描述 |
---|---|---|---|
CONSTANT_Utf8_info | tag | u1 | 1 |
length | u2 | UTF-8编码的字符串长度 | |
bytes | u1 | 长度为length的UTF-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_NameAndTyperef_info | tag | u1 | 12 |
index | u2 | 指向表示该字段或方法名称的常量项的索引 | |
index | u2 | 指向表示该字段或方法描述符的常量项的索引 | |
CONSTANT_MethodHandle_info | tag | u1 | 15 |
reference_kind | u1 | 值在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_methods[]数组的有效索引 | |
name_and_type_index | u2 | 值必须是对当前常量池的有效索引,常量池在该索引处的项必须是CONSTANT_NameAndType_info结构,表示方法名和方法描述符 |
我们暂时先关注这个表中每一行项目列的tag
的值,这是一个u1类型的值,常量池中每一项常量的开始位置都以该值填充。
4.1. 第1个常量分析
以前面的例子来说,在常量池计数器值(第9和第10位)之后的第一个u1类型(即第11位)为0x0A(十进制为10),如下所示:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33 00 30(0A)00 06 00 25 08 J~:>...3.0....%.
- ...
对照表2可知,该值表示接下来的常量为CONSTANT_Methodref_info类型,该类型的定义如下:
常量 | 项目 | 类型 | 描述 |
---|---|---|---|
CONSTANT_Methodref_info | tag | u1 | 10 |
index | u2 | 指向声明方法的类描述符CONSTANT_Class_info的索引项 | |
index | u2 | 指向名称及类型描述符CONSTANT_NameAndType_info的索引项 |
CONSTANT_Methodref_info类型除了tag
标志位,还需要有两个u2类型的index
索引为分别指向声明方法的类描述符CONSTANT_Class_info的索引项和名称及类型描述符CONSTANT_NameAndType_info的索引项,这两个index
索引的位置如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33 00 30 0A(00 06 00 25)08 J~:>...3.0....%.
- ...
即0x0006(十进制为6),指向常量池的第#5项的索引;和0x0025(十进制为37),指向常量池的第#37项索引。
4.2. 第2个常量分析
接下来我们分析第二个常量,先加上第二行的字节码数据:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33 00 30 0A 00 06 00 25(08) J~:>...3.0....%.
- 00000010: 00 26 07 00 27 0A 00 03 00 28 07 00 29 07 00 2A .&..'....(..)..*
- ...
第二个常量的标志位tag
值为0x08(十进制为8),通过查表2可知,该值表示接下来的常量为CONSTANT_String_info类型,该类型的定义如下:
常量 | 项目 | 类型 | 描述 |
---|---|---|---|
CONSTANT_String_info | tag | u1 | 8 |
index | u2 | 指向字符串字面量的索引 |
同样的,还需要有一个u2类型的index
索引为分别指向代表字符串字面量的常量的索引,该index
索引的位置如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33 00 30 0A 00 06 00 25 08 J~:>...3.0....%.
- 00000010:(00 26)07 00 27 0A 00 03 00 28 07 00 29 07 00 2A .&..'....(..)..*
- ...
即0x0026(十进制为38),指向常量池的第#38项常量的索引。
4.3. 第3个常量分析
接下来我们分析第三个常量:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33 00 30 0A 00 06 00 25 08 J~:>...3.0....%.
- 00000010: 00 26(07)00 27 0A 00 03 00 28 07 00 29 07 00 2A .&..'....(..)..*
- ...
第三个常量的标志位tag
值为0x07(十进制为7),通过查表2可知,该值表示接下来的常量为CONSTANT_Class_info类型,该类型的定义如下:
常量 | 项目 | 类型 | 描述 |
---|---|---|---|
CONSTANT_Class_info | tag | u1 | 7 |
index | u2 | 指向表示全限定名的常量项的索引 |
同样的,还需要有一个u2类型的index
索引指向字符串字面量的索引项,这个index
索引的位置如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000000: CA FE BA BE 00 00 00 33 00 30 0A 00 06 00 25 08 J~:>...3.0....%.
- 00000010: 00 26 07(00 27)0A 00 03 00 28 07 00 29 07 00 2A .&..'....(..)..*
- ...
即0x0027(十进制为39),指向常量池的第#39项常量的索引。
4.4. 剩余常量分析
分析完上面三个常量,大家应该能够明白常量池中常量项目的组织原理了;对照二进制字节码来分析并不是一个明智的选择,因为数据太多,需要一项一项地逐个计算;我们可以使用javap
反编译工具来协助做这种分析工作,对编译得到的DecompileTest.class文件进行反编译,可以得到下面的内容:
- $ > javap -verbose ClassFileTest.class
- Classfile /Users/LennonChin/Downloads/Google Driver/Blog/Test/ClassFileTest.class
- Last modified 2015-7-4; size 972 bytes
- MD5 checksum 94a03d5ec766c7ac78dbaa5109957b64
- Compiled from "ClassFileTest.java"
- public class ClassFileTest<T extends java.lang.Object> extends java.lang.Object implements java.io.Serializable, java.lang.Cloneable
- Signature: #34 // <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Cloneable;
- SourceFile: "ClassFileTest.java"
- InnerClasses:
- #10= #9 of #5; //InnerClass=class ClassFileTest$InnerClass of class ClassFileTest
- minor version: 0
- major version: 51
- flags: ACC_PUBLIC, ACC_SUPER
- Constant pool:
- #1 = Methodref #6.#37 // java/lang/Object."<init>":()V
- #2 = String #38 // 2
- #3 = Class #39 // java/lang/ArithmeticException
- #4 = Methodref #3.#40 // java/lang/ArithmeticException.printStackTrace:()V
- #5 = Class #41 // ClassFileTest
- #6 = Class #42 // java/lang/Object
- #7 = Class #43 // java/io/Serializable
- #8 = Class #44 // java/lang/Cloneable
- #9 = Class #45 // ClassFileTest$InnerClass
- #10 = Utf8 InnerClass
- #11 = Utf8 InnerClasses
- #12 = Utf8 t
- #13 = Utf8 Ljava/lang/Object;
- #14 = Utf8 Signature
- #15 = Utf8 TT;
- #16 = Utf8 i
- #17 = Utf8 I
- #18 = Utf8 ConstantValue
- #19 = Integer 1
- #20 = Utf8 <init>
- #21 = Utf8 ()V
- #22 = Utf8 Code
- #23 = Utf8 LineNumberTable
- #24 = Utf8 main
- #25 = Utf8 ([Ljava/lang/String;)V
- #26 = Utf8 divide
- #27 = Utf8 (II)D
- #28 = Utf8 StackMapTable
- #29 = Class #39 // java/lang/ArithmeticException
- #30 = Class #46 // java/lang/Throwable
- #31 = Utf8 Deprecated
- #32 = Utf8 RuntimeVisibleAnnotations
- #33 = Utf8 Ljava/lang/Deprecated;
- #34 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Cloneable;
- #35 = Utf8 SourceFile
- #36 = Utf8 ClassFileTest.java
- #37 = NameAndType #20:#21 // "<init>":()V
- #38 = Utf8 2
- #39 = Utf8 java/lang/ArithmeticException
- #40 = NameAndType #47:#21 // printStackTrace:()V
- #41 = Utf8 ClassFileTest
- #42 = Utf8 java/lang/Object
- #43 = Utf8 java/io/Serializable
- #44 = Utf8 java/lang/Cloneable
- #45 = Utf8 ClassFileTest$InnerClass
- #46 = Utf8 java/lang/Throwable
- #47 = Utf8 printStackTrace
- {
- T t;
- flags:
- Signature: #15 // TT;
- public static final int i;
- flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL
- ConstantValue: int 1
- public ClassFileTest();
- flags: ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."<init>":()V
- 4: return
- LineNumberTable:
- line 3: 0
- line 25: 4
- public static void main(java.lang.String[]);
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=1, locals=2, args_size=1
- 0: ldc #2 // String 2
- 2: astore_1
- 3: return
- LineNumberTable:
- line 8: 0
- line 9: 3
- public double divide(int, int);
- flags: ACC_PUBLIC
- Code:
- stack=4, locals=9, args_size=3
- 0: iload_1
- 1: i2d
- 2: dconst_1
- 3: dmul
- 4: iload_2
- 5: i2d
- 6: ddiv
- 7: dstore_3
- 8: dload_3
- 9: dstore 5
- 11: dconst_0
- 12: dstore_3
- 13: dload 5
- 15: dreturn
- 16: astore 5
- 18: aload 5
- 20: invokevirtual #4 // Method java/lang/ArithmeticException.printStackTrace:()V
- 23: dconst_0
- 24: dstore 6
- 26: dconst_0
- 27: dstore_3
- 28: dload 6
- 30: dreturn
- 31: astore 8
- 33: dconst_0
- 34: dstore_3
- 35: aload 8
- 37: athrow
- Exception table:
- from to target type
- 0 11 16 Class java/lang/ArithmeticException
- 0 11 31 any
- 16 26 31 any
- 31 33 31 any
- LineNumberTable:
- line 15: 0
- line 16: 8
- line 21: 11
- line 17: 16
- line 18: 18
- line 19: 23
- line 21: 26
- StackMapTable: number_of_entries = 2
- frame_type = 80 /* same_locals_1_stack_item */
- stack = [ class java/lang/ArithmeticException ]
- frame_type = 78 /* same_locals_1_stack_item */
- stack = [ class java/lang/Throwable ]
- Deprecated: true
- RuntimeVisibleAnnotations:
- 0: #33()
- }
其中显示的以下内容即是30个常量的编号、类型、引用的相关索引等信息:
- Constant pool:
- #1 = Methodref #6.#37 // java/lang/Object."<init>":()V
- #2 = String #38 // 2
- #3 = Class #39 // java/lang/ArithmeticException
- #4 = Methodref #3.#40 // java/lang/ArithmeticException.printStackTrace:()V
- #5 = Class #41 // ClassFileTest
- #6 = Class #42 // java/lang/Object
- #7 = Class #43 // java/io/Serializable
- #8 = Class #44 // java/lang/Cloneable
- #9 = Class #45 // ClassFileTest$InnerClass
- #10 = Utf8 InnerClass
- #11 = Utf8 InnerClasses
- #12 = Utf8 t
- #13 = Utf8 Ljava/lang/Object;
- #14 = Utf8 Signature
- #15 = Utf8 TT;
- #16 = Utf8 i
- #17 = Utf8 I
- #18 = Utf8 ConstantValue
- #19 = Integer 1
- #20 = Utf8 <init>
- #21 = Utf8 ()V
- #22 = Utf8 Code
- #23 = Utf8 LineNumberTable
- #24 = Utf8 main
- #25 = Utf8 ([Ljava/lang/String;)V
- #26 = Utf8 divide
- #27 = Utf8 (II)D
- #28 = Utf8 StackMapTable
- #29 = Class #39 // java/lang/ArithmeticException
- #30 = Class #46 // java/lang/Throwable
- #31 = Utf8 Deprecated
- #32 = Utf8 RuntimeVisibleAnnotations
- #33 = Utf8 Ljava/lang/Deprecated;
- #34 = Utf8 <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Cloneable;
- #35 = Utf8 SourceFile
- #36 = Utf8 ClassFileTest.java
- #37 = NameAndType #20:#21 // "<init>":()V
- #38 = Utf8 2
- #39 = Utf8 java/lang/ArithmeticException
- #40 = NameAndType #47:#21 // printStackTrace:()V
- #41 = Utf8 ClassFileTest
- #42 = Utf8 java/lang/Object
- #43 = Utf8 java/io/Serializable
- #44 = Utf8 java/lang/Cloneable
- #45 = Utf8 ClassFileTest$InnerClass
- #46 = Utf8 java/lang/Throwable
- #47 = Utf8 printStackTrace
我们主要补充讲解一下第#10个常量的组织形式,从上面的反编译结果可以得知该常量类型为CONSTANT_Utf8_info,表示字符串InnerClass
,定义如下:
常量 | 项目 | 类型 | 描述 |
---|---|---|---|
CONSTANT_Utf8_info | tag | u1 | 1 |
length | u2 | UTF-8编码的字符串长度 | |
bytes | u1 | 长度为length的UTF-8编码的字符串 |
在二进制字节码中显示的数码如下:
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000020: 07 00 2B 07 00 2C 07 00 2D(01 00 0A 49 6E 6E 65 ..+..,..-...Inne
- 00000030: 72 43 6C 61 73 73)01 00 0C 49 6E 6E 65 72 43 6C rClass...InnerCl
- ...
其中0x01(十进制为1)表示tag
标志位为1;接下来是length
位,为u2类型,即0x000A(十进制为10),表示是该UTF-8字符串的长度为10;根据length
的值,我们需要向后找长度为1的字符作为bytes
数组,值即为UTF-8字符串的内容,也即是数码49 6E 6E 65 72 43 6C 61 73 73
,该数码表示为ASCII码时即为字符串InnerClass
。
4.5. 反推字节码
通过这种方式,我们其实可以通过反编译结果反推对应的常量在字节码中的位置;比如第#11号常量为CONSTANT_Utf8_info类型,表示字符串InnerClasses
,因此它的tag
编码应该是0x01,同时它的长度是12,因此length
编码应该是0x000C,且InnerClasses
中第一个字符I
的ASCII码为十进制73(十六进制为0x49),我们可以根据01 00 0C 49
来搜索,即可搜索到对应内容01 00 0C 49 6E 6E 65 72 43 6C 61 73 73 65 73
:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000030: 72 43 6C 61 73 73(01 00 0C 49 6E 6E 65 72 43 6C rClass...InnerCl
- 00000040: 61 73 73 65 73)01 00 01 74 01 00 12 4C 6A 61 76 asses...t...Ljav
- ...
其中49 6E 6E 65 72 43 6C 61 73 73 65 73
表示组成字符串InnerClasses
的13个字符。
5. 访问标志
分析完常量池,接下来我们看访问标志(access_flags)。访问标志紧跟在常量池之后,用两个字节来表示,标识了类或者接口的访问信息,比如:该Class文件是类还是接口,是否被定义成public,是否是abstract,如果是类,是否被声明成final等等。各种访问标志如下所示:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 是否为public类型 |
ACC_FINAL | 0x0010 | 是否被声明为final,只有类可以设置 |
ACC_SUPER | 0x0020 | 是否允许使用invokespecial字节码指令的新语义,invokespecial指令的语义在JDK 1.0.2之后发生过改变,为了区别这条指令使用哪种语义,JDK 1.0.2之后编译出来的类的这个标志都必须为真 |
ACC_INTERFACE | 0x0200 | 标志这是一个接口 |
ACC_ABSTRACT | 0x0400 | 是否为abstract类型,对于接口或者抽象类来说,此标志值为真,其他类值为假 |
ACC_SYNTHETIC | 0x1000 | 标志这个类并非由用户代码产生 |
ACC_ANNOTATION | 0x2000 | 标志这是一个注解 |
ACC_ENUM | 0x4000 | 标志这是一个枚举 |
从这几个标志的值可以看出,它们的取值是有特点的,可以保证使用或运算对不重复的多个标志位进行计算时,结果值依旧能够清晰地表达所有参与运算的标志位。在我们的例子中,ClassFileTest类是一个普通类,同时被public修饰,但没有被final修饰,因此它的访问标志应该是ACC_PUBLIC | ACC_SUPER
,也即是0x0001 | 0x0020
,最后的访问标志值应该为0x0021
。在字节码文件中,表示为以下内容:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000270: 63 6B 54 72 61 63 65(00 21)00 05 00 06 00 02 00 ckTrace.!.......
- ...
6. 类索引、父类索引、接口索引集合
在访问标志位之后,分别是类索引(u2类型)、父类索引(u2类型)和接口索引集合;其中紧跟着访问标志位的两个字节就是类索引,类索引后的两个字节就是父类索引,父类索引后的两个字节则是接口索引集合计数器。通过这三项,就可以确定了这个类的继承关系了;对应的字节码内容如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000270: 63 6B 54 72 61 63 65 00 21(00 05 00 06 00 02 00 ckTrace.!.......
- 00000280: 07 00 08)00 02 00 00 00 0C 00 0D 00 01 00 0E 00 ................
- ...
从字节码内容可知,该文件的类索引、父类索引和接口索引集合的十六进制值分别是0x0005
(十进制值为4)、0x0006
(十进制值为6)和0x0002
(十进制值为2);从前面分析的常量池可知,其中#5和#6号常量都是CONSTANT_Class_info类型的常量,在常量池中的描述符引用项分别是#41和#42,分别是字符串ClassFileTest
和java/lang/Object
:
- Constant pool:
- #5 = Class #41 // ClassFileTest
- #6 = Class #42 // java/lang/Object
- ...
- #41 = Utf8 ClassFileTest
- #42 = Utf8 java/lang/Object
- ...
这与ClassFileTest类的定义是可以对应上的,ClassFileTest类是一个普通类,继承了Object类;同时ClassFileTest类实现了Serializable和Cloneable两个接口后,因此它的接口索引集合的值为0x0002
,表示实现了两个接口,同时在0x0002
之后的0x0007
和0x0008
分别表示这两个接口的声明所对应的常量索引项:
- Constant pool:
- ...
- #7 = Class #43 // java/io/Serializable
- #8 = Class #44 // java/lang/Cloneable
- ...
- #43 = Utf8 java/io/Serializable
- #44 = Utf8 java/lang/Cloneable
如果一个类没有实现任何接口,它的接口索引集合值会为0,也不会有后面的标识接口的字节了。
7. 字段表集合
字段表(field_info)用于描述接口或者类中声明的变量。字段(field)包括类级变量以及实例级变量,但不包括在方法内部声明的局部变量。
在接口计数器或接口索引集合后面就是字段表集合了,字段表集合的描述中,首先使用了一个u2类型的字段计数器表示字段表的个数,对应的字节码内容如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000280: 07 00 08(00 02)00 00 00 0C 00 0D 00 01 00 0E 00 ................
0x0002
表示在该类中有2个字段表,对应2个字段,从类的源码可知,正好有t
、i
2个字段。
字段计数器之后就是对各个字段表的详细描述了。首先需要清楚对于字段应该描述的信息包括以下几类:
- 字段的作用域(public、private、protected修饰符);
- 是实例变量还是类变量(static修饰符);
- 可变性(final);
- 并发可见性(volatile修饰符,是否强制从主内存读写);
- 可否被序列化(transient修饰符);
- 字段数据类型(基本类型、对象、数组);
- 字段名称。
其中,修饰符都是布尔值,要么有要么没有,适合使用标志位来表示,而其它的不固定的信息需要引用常量池中的常量来描述。下表是字段表的结构:
类型 | 名称 | 含义 | 数量 |
---|---|---|---|
u2 | access_flags | 访问标志 | 1 |
u2 | name_index | 字段名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 属性计数器 | 1 |
attribute_info | attributes | 属性集合 | attributes_count |
访问标志access_flags
是一个修饰符,它可以使用下面的访问标志表进行描述:
标志名称 | 标志值 | 含义 |
---|---|---|
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_TRANSTENT | 0x0080 | 字段是否为transient |
ACC_SYNCHETIC | 0x1000 | 字段是否为由编译器自动产生 |
ACC_ENUM | 0x4000 | 字段是否为enum |
在实际情况中,ACC_PUBLIC、ACC_PRIVATE、ACC_PROTECTED三个标志最多只能选择其一,ACC_FINAL、ACC_VOLATILE不能同时选择。接口之中的字段必须有ACC_PUBLIC、ACC_STATIC、ACC_FINAL标志,这些都是由Java本身的语言规则所决定的。
字段名索引name_index
是一个u2类型的值,它指向了常量池中某个常量的索引,用于表示字段的简单名称。descriptor_index
与name_index
类似,不过它用于表示字段的描述符。
这里需要说明一下全限定名、简单名称及描述符的区别。全限定名,类似于java/lang/Object
,为了使连续的多个全限定名之间不产生混淆,在使用时最后一般会加入一个;
表示全限定名结束。
简单名称,指没有类型和参数修饰的方法或者字段名称,这个类中的方法double divide(int number1, int number2)
和字段i
的简单名称分别是double
和i
。
描述符,是用来描述字段的数据类型、方法的参数列表(包括数量、类型以及顺序)和返回值。根据描述符规则,基本数据类型(byte、char、double、float、int、long、short、boolean)以及代表无返回值的void类型都用一个大写字符来表示,而对象类型则用字符L加对象的全限定名来表示,如下表:
字符 | 含义 |
---|---|
B | 基本类型byte |
C | 基本类型char |
D | 基本类型double |
F | 基本类型float |
I | 基本类型int |
J | 基本类型long |
S | 基本类型short |
Z | 基本类型boolean |
V | 基本类型void |
L | 对象类型 |
数组类型的描述符,每一维度用一个[
表示,如Object类型的一位数组会被表示为[Ljava/lang/Object;
,一个int类型的二位数组会被表示为[[I
。
方法的描述符,先表示参数列表,后表示返回值类型;参数列表放在一对括号中。如double divide(int number1, int number2)
方法会被表示为(II)D
,再比如int indexOf(char[]source, int sourceOffset, int sourceCount, char[]target, int targetOffset, int targetCount, int fromIndex)
的描述符为([CII[CIII)I
。
接下来我们开始分析ClassFileTest类的字节码文件中的字段表信息。
之前我们从字节码内容已经知道,在ClassFileTest中有2个字段,我们开始分析第一个字段,对应的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000280: 07 00 08 00 02(00 00 00 0C 00 0D 00 01)00 0E 00 ................
- ...
- 访问标志位(u2类型)为
0x0000
,表示没有任何访问修饰; - 字段名索引(u2类型)为
0x000C
(十进制值为12),即引用常量池中#12号常量,即字符t
,表示该字段的名称; - 描述符索引(u2类型)为
0x000D
(十进制值为13),即引用常量池中#13号常量,即字符Ljava/lang/Object
,表示该字段为Object类型; - 属性计数器(u2类型)为
0x0001
,表示该字段有1项属性信息。关于属性性信息后面会介绍。
这里描述的第一个字段很明显就是T t;
声明的字段了。
接下来是第二个字段,对应的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000290: 00 00 02 00 0F(00 19 00 10 00 11 00 01)00 12 00 ................
- ...
- 访问标志位(u2类型)为
0x0019
,即为0x0001 | 0x0008 | 0x0010
的值,对照访问标志表可知,分别表示了public、static和final的访问修饰符; - 字段名索引(u2类型)为
0x0010
(十进制值为16),即引用常量池中#16号常量,即字符i
,表示该字段的名称; - 描述符索引(u2类型)为
0x0011
(十进制值为17),即引用常量池中#17号常量,即字符I
,表示该字段为int类型; - 属性计数器(u2类型)为
0x0001
,表示该字段有1项属性信息。关于属性性信息后面会介绍。
这里声明的自然是int类型的字段i
了。
上面两个字段涉及到的常量如下:
- Constant pool:
- ...
- #12 = Utf8 t
- #13 = Utf8 Ljava/lang/Object;
- ...
- #16 = Utf8 i
- #17 = Utf8 I
- ...
字段表集合中不会列出从超类或者父接口中继承而来的字段,但有可能列出原本Java代码之中不存在的字段,譬如在内部类中为了保持对外部类的访问性,会自动添加指向外部类实例的字段。另外,在Java语言中字段是无法重载的,两个字段的数据类型、修饰符不管是否相同,都必须使用不一样的名称,但是对于字节码来讲,如果两个字段的描述符不一致,那字段重名就是合法的。
8. 方法表集合
方法表集合与字段表集合非常类似,首先会使用一个u2类型的字节码表示方法的个数,接下来才是各个方法的描述信息。对于方法的描述,与对字段的描述非常类似,方法表的结构如下:
类型 | 名称 | 含义 | 数量 |
---|---|---|---|
u2 | access_flags | 访问标志 | 1 |
u2 | name_index | 方法名索引 | 1 |
u2 | descriptor_index | 描述符索引 | 1 |
u2 | attributes_count | 属性计数器 | 1 |
attribute_info | attributes | 属性集合 | attributes_count |
其中访问标志access_flags
与字段表不一样,方法没有了ACC_VOLATILE和ACC_TRANSTENT的修饰,同时对方法的描述多了ACC_SYHCHRONRIZED、ACC_NATIVE,ACC_ABSTRACT和ACC_STRICTFP几类;如下表所示:
标志名称 | 标志值 | 含义 |
---|---|---|
ACC_PUBLIC | 0x0001 | 方法是否为public |
ACC_PRIVATE | 0x0002 | 方法是否为private |
ACC_PROTECTED | 0x0004 | 方法是否为protected |
ACC_STATIC | 0x0008 | 方法是否为static |
ACC_FINAL | 0x0010 | 方法是否为final |
ACC_SYHCHRONRIZED | 0x0020 | 方法是否为synchronized |
ACC_BRIDGE | 0x0040 | 方法是否是有编译器产生的方法 |
ACC_VARARGS | 0x0080 | 方法是否接受参数 |
ACC_NATIVE | 0x0100 | 方法是否为native |
ACC_ABSTRACT | 0x0400 | 方法是否为abstract |
ACC_STRICTFP | 0x0800 | 方法是否为strictfp |
ACC_SYNTHETIC | 0x1000 | 方法是否是有编译器自动产生的 |
对于方法体的内容,都存放在一个名为Code
的属性中。
在我们的ClassFileTest类的字节码信息中,有如下信息:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 000002a0: 00 00 02 00 13(00 03)00 01 00 14 00 15 00 01 00 ................
- ...
其中0x0003
就是表示方法个数的计数器值,十进制为3,即表示一共有三个方法,具体是哪三个方法将在下面进行分析
8.1. 第一个方法
下面我们开始分析第一个方法的信息,在紧跟着方法计数器之后,有以下字节码:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 000002a0: 00 00 02 00 13 00 03(00 01 00 14 00 15 00 01 00 ................
- 000002b0: 16)00 00 00 21 00 01 00 01 00 00 00 05 2A B7 00 ....!........*7.
- ...
0x0001
(十进制值为1)表示该方法的访问标志ACC_PUBLIC,即public;0x0014
(十进制值为20)为表示方法名称的常量索引项,即#20号常量,为字符串<init>
,表示这是一个构造方法;0x0015
(十进制值为21)为表示方法描述符的常量索引项,即#21号常量,为字符串()V
;0x0001
(十进制值为1)表示属性项的个数,即只有1项属性;0x0016
(十进制值为22)为上两个字节提到的那1项属性的常量索引,即#22号常量,对应于常量为字符串Code
,常量Code
字符串所代表的信息会在后面的属性表中讲解。
这个部分信息描述的是默认的无参构造方法,虽然在类中没有定义,但编译器会默认添加。
与字段表集合相对应的,如果父类方法在子类中没有被重写(Override),方法表集合中就不会出现来自父类的方法信息。但同样的,有可能会出现由编译器自动添加的方法,最典型的便是类构造器<clinit>
方法和实例构造器<init>
方法。
8.2. 第二个方法
从方法的计数器值可知字节码中描述了三个方法。第二个方法的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 000002d0: 00 03 00 04 00 19(00 09 00 18 00 19 00 01 00 16) ................
- 000002e0: 00 00 00 20 00 01 00 02 00 00 00 04 12 02 4C B1 ..............L1
- ...
0x0009
(十进制值为1)表示该方法的访问标志ACC_PUBLIC和ACC_STATIC,即public static;0x0018
(十进制值为24)为表示方法名称的常量索引项,即#24号常量,为字符串main
;0x0019
(十进制值为25)为表示方法描述符的常量索引项,即#25号常量,为字符串([Ljava/lang/String;)V
,描述了方法的参数和返回值;0x0001
(十进制值为1)表示属性项的个数,即只有1项属性;0x0016
(十进制值为22)为上两个字节提到的那1项属性的常量索引,即#22号常量,对应于常量为字符串Code
,常量Code
字符串所代表的信息会在后面的属性表中讲解。
可知,第二个方法描述的即是main方法public static void main(String[] args)
。
8.3. 第三个方法
第三个方法的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000300: 00 03 00 09(00 01 00 1A 00 1B 00 03 00 16)00 00 ................
- ...
0x0001
(十进制值为1)表示该方法的访问标志ACC_PUBLIC,即public;0x001A
(十进制值为26)为表示方法名称的常量索引项,即#26号常量,为字符串divide
;0x001B
(十进制值为27)为表示方法描述符的常量索引项,即#27号常量,为字符串(II)D
,描述了方法的参数和返回值;0x0003
(十进制值为1)表示属性项的个数,即有3项属性;0x0016
(十进制值为17)为上两个字节提到的那1项属性的常量索引,即#17号常量,对应于常量为字符串Code
,常量Code
字符串所代表的信息会在后面的属性表中讲解。
可知,第三个方法描述的即是方法public double divide(int number1, int number2)
。
从上面的分析可知,其实常量池中的常量是会被复用的。如四个方法的属性常量字符串Code
。
另外,我们从javap
命令反编译的结果中也可以得到一些方法的定义信息:
- public ClassFileTest();
- flags: ACC_PUBLIC
- Code:
- stack=1, locals=1, args_size=1
- 0: aload_0
- 1: invokespecial #1 // Method java/lang/Object."<init>":()V
- 4: return
- LineNumberTable:
- line 3: 0
- line 25: 4
- public static void main(java.lang.String[]);
- flags: ACC_PUBLIC, ACC_STATIC
- Code:
- stack=1, locals=2, args_size=1
- 0: ldc #2 // String 2
- 2: astore_1
- 3: return
- LineNumberTable:
- line 8: 0
- line 9: 3
- public double divide(int, int);
- flags: ACC_PUBLIC
- Code:
- stack=4, locals=9, args_size=3
- 0: iload_1
- 1: i2d
- 2: dconst_1
- 3: dmul
- 4: iload_2
- 5: i2d
- 6: ddiv
- 7: dstore_3
- 8: dload_3
- 9: dstore 5
- 11: dconst_0
- 12: dstore_3
- 13: dload 5
- 15: dreturn
- 16: astore 5
- 18: aload 5
- 20: invokevirtual #4 // Method java/lang/ArithmeticException.printStackTrace:()V
- 23: dconst_0
- 24: dstore 6
- 26: dconst_0
- 27: dstore_3
- 28: dload 6
- 30: dreturn
- 31: astore 8
- 33: dconst_0
- 34: dstore_3
- 35: aload 8
- 37: athrow
- Exception table:
- from to target type
- 0 11 16 Class java/lang/ArithmeticException
- 0 11 31 any
- 16 26 31 any
- 31 33 31 any
- LineNumberTable:
- line 15: 0
- line 16: 8
- line 21: 11
- line 17: 16
- line 18: 18
- line 19: 23
- line 21: 26
- StackMapTable: number_of_entries = 2
- frame_type = 80 /* same_locals_1_stack_item */
- stack = [ class java/lang/ArithmeticException ]
- frame_type = 78 /* same_locals_1_stack_item */
- stack = [ class java/lang/Throwable ]
- Deprecated: true
- RuntimeVisibleAnnotations:
- 0: #33()
注:在Java语言中,要重载(Overload)一个方法,除了要与原方法具有相同的简单名称之外,还要求必须拥有一个与原方法不同的特征签名,特征签名就是一个方法中各个参数在常量池中的字段符号引用的集合,也就是因为返回值不会包含在特征签名中,因此Java语言里面是无法仅仅依靠返回值的不同来对一个已有方法进行重载的。但是在Class文件格式中,特征签名的范围更大一些,只要描述符不是完全一致的两个方法也可以共存。也就是说,如果两个方法有相同的名称和特征签名,但返回值不同,那么也是可以合法共存于同一个Class文件中的。
9. 属性表集合
属性表(attribute_info)用于描述某些场景专有的信息,在Class文件、字段表、方法表都可以携带自己的属性表集合。
属性表集合的限制稍微宽松了一些,不再要求各个属性表具有严格顺序,并且只要不与已有属性名重复,任何人实现的编译器都可以向属性表中写入自己定义的属性信息,Java虚拟机运行时会忽略掉它不认识的属性。最新的《Java虚拟机规范(Java SE 7)》版中,预定义属性有21项,如下表所示:
属性名称 | 使用位置 | 含义 |
---|---|---|
Code | 方法表 | Java代码编译成的字节码指令 |
ConstantValue | 字段表 | final关键字定义的常量值 |
Deprecated | 类、方法、字段表 | 被声明为deprecated的方法和字段 |
Exceptions | 方法表 | 方法抛出的异常 |
EnclosingMethod | 类文件 | 仅当一个类为局部类或者匿名类是才能拥有这个属性,这个属性用于标识这个类所在的外围方法 |
InnerClass | 类文件 | 内部类列表 |
LineNumberTable | Code属性 | Java源码的行号与字节码指令的对应关系 |
LocalVariableTable | Code属性 | 方法的局部变量描述 |
StackMapTable | Code属性 | JDK 1.6中新增的属性,供新的类型检查检验器(Type Checker)检查和处理目标方法的局部变量和操作数有所需要的类是否匹配 |
Signature | 类、方法表、字段表 | JDK 1.5中新增的属性,用于支持泛型情况下的方法签名,在Java语言中,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。由于Java的泛型采用擦除法实现,在为了避免类型信息被擦除后导致签名混乱,需要这个属性记录泛型中的相关信息 |
SourceFile | 类文件 | 记录源文件名称 |
SourceDebugExtension | 类文件 | JDK 1.6中新增的属性,用于存储额外的调试信息。譬如在进行JSP文件调试时,无法通过Java堆栈来定位到JSP文件的行号,JSR-45规范为这些非Java语言编写,却需要编译成字节码并允许在Java虚拟机中的程序提供了一个进行调试的标准机制,使用SourceDebugExtension属性就可以用于存储这个标准所新加入的调试信息 |
Synthetic | 类,方法表,字段表 | 标志方法或字段为编译器自动生成的 |
LocalVariableTypeTable | 类 | JDK 1.5中新增的属性,使用特征签名代替描述符,是为了引入泛型语法之后能描述泛型参数化类型而添加 |
RuntimeVisibleAnnotations | 类、方法表、字段表 | JDK 1.5中新增的属性,为动态注解提供支持。用于指明哪些注解是运行时(实际上运行时就是进行反射调用)可见的 |
RuntimeInvisibleAnnotations | 表、方法表、字段表 | JDK 1.5中新增的属性,与RuntimeVisibleAnnotations相反,用于指明哪些注解是运行时不可见的 |
RuntimeVisibleParameterAnnotation | 方法表 | JDK 1.5中新增的属性,作用与RuntimeVisibleAnnotations属性类似,只不过作用对象为方法 |
RuntimeInvisibleParameterAnnotation | 方法表 | JDK 1.5中新增的属性,作用与RuntimeInvisibleAnnotations属性类似,作用对象哪个为方法参数 |
AnnotationDefault | 方法表 | JDK 1.5中新增的属性,用于记录注解类元素的默认值 |
BootstrapMethods | 类文件 | JDK 1.7中新增的属性,用于保存invokeddynamic指令引用的引导方式限定符 |
每个属性由三部分构成:属性名索引、属性值长度及属性信息表:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性值长度 |
u1 | info | attribute_length | 属性信息表 |
其中,attribute_name_index
和attribute_length
是需要以固定方式进行说明的,info
内容则是完全自定义,保持总长度与attribute_length
所规定的一致即可。
9.1. Code属性
方法体中的代码经过编译后变为字节码指令存储在Code属性内,该属性最终被描述为一张Code属性表,由于Code属性表用于存放方法体的相关信息,因此接口或者抽象类中的方法就不存在Code属性表。
由于Code属性表是一张属性表,它的前两个项必然是attribute_name_index
和attribute_length
,第三项info
是完全自定义的;结构如下表所示:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | max_stack | 1 | 操作数栈深度的最大值 |
u2 | max_locals | 1 | 局部变量表所需的存续空间 |
u4 | code_length | 1 | 字节码指令的长度 |
u1 | code | code_length | 存储字节码指令 |
u2 | exception_table_length | 1 | 异常表长度 |
exception_info | exception_table | exception_length | 异常表 |
u2 | attributes_count | 1 | 属性集合计数器 |
attribute_info | attributes | attributes_count | 属性集合 |
Code属性表的前两项跟属性表是一致的,即Code属性表遵循属性表的结构,后面那些则是他自定义的结构。
以前面分析的第三个方法public double divide(int number1, int number2)
的方法表为例:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000300: 00 03 00 09 00 01 00 1A 00 1B 00 03(00 16 00 00 ................
- 00000310: 00 86 00 04 00 09 00 00 00 26 1B 87 0F 6B 1C 87 .........&...k..
- 00000320: 6F 4A 29 39 05 0E 4A 18 05 AF 3A 05 19 05 B6 00 oJ)9..J../:...6.
- 00000330: 04 0E 39 06 0E 4A 18 06 AF 3A 08 0E 4A 19 08 BF ..9..J../:..J..?
- 00000340: 00 04 00 00 00 0B 00 10 00 03 00 00 00 0B 00 1F ................
- 00000350: 00 00 00 10 00 1A 00 1F 00 00 00 1F 00 21 00 1F .............!..
- 00000360: 00 00 00 02 00 17 00 00 00 1E 00 07 00 00 00 0F ................
- 00000370: 00 08 00 10 00 0B 00 15 00 10 00 11 00 12 00 12 ................
- 00000380: 00 17 00 13 00 1A 00 15)00 1C 00 00 00 0A 00 02 ................
- ...
其中包括0x0016
以前的内容已经分析过了,这里着重分析Code属性表的各项:
0x0016
:u2类型的attribute_name_index
项,表示常量字符串Code
的索引;0x0000 0086
:u4类型的attribute_length
,表示Code这一项属性的长度;十进制值为134,即从下一个字节开始,连续的134个字节都用于描述该方法的信息;0x0004
:u2类型的max_stack
,表示该方法的操作数栈最大深度;十进制值为4,表示最大栈深为4;0x0009
:u2类型的max_locals
,表示局部变量表所需的存续空间;十进制值为1,表示需要1个字节的局部变量表存续空间;存续空间的单位是Slot,Slot是虚拟机为局部变量分配内存所使用的最小单位。0x0000 0026
:u4类型的code_length
,表示字节码指令的长度;十进制值为38,即从下一个字节开始,连续的38个字节都是字节码指令;1B 87 0F 6B 1C 87 6F 4A 29 39 05 0E 4A 18 05 AF 3A 05 19 05 B6 00 04 0E 39 06 0E 4A 18 06 AF 3A 08 0E 4A 19 08 BF
:这连续的38个字节是字节码指令code
。一个字节代表一个指令,一个指令可能有参数也可能没参数,如果有参数,则其后面字节码就是他的参数;如果没参数,后面的字节码就是下一条指令。关于字节码指令在后面会专门介绍。0x0004
:u2类型的exception_table_length
,十进制值为4,即表示该方法还存在4个异常信息表。
由于还存在异常信息表,接下来我们开始分析异常表。
9.1.1. 异常表
每张异常表包含4个字段,结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | start_pc | 1 | try起始行 |
u2 | end_pc | 1 | try结束行 |
u2 | handler_pc | 1 | 遇到异常情况时进行处理的行 |
u2 | catch_type | 1 | 异常类型,指向一个CONSTANT_Class_info型常量的索引 |
这些字段的含义为:如果当字节码在第start_pc
行到第end_pc
行之间(不含第end_pc
行)出现了类型为catch_type
或者其子类的异常(catch_type
为指向一个CONSTANT_Class_info型常量的索引),则转到第handler_pc
行继续处理。当catch_type
的值为0时,代表任意异常情况都需要转向到handler_pc
处进行处理。
异常表实际上是Java代码的一部分,编译器使用异常表而不是简单的跳转命令来实现Java异常及finally处理机制。
在前面分析的内容中得知方法public double divide(int number1, int number2)
有4项异常信息表,由于每张表分别有4个u2类型的字段构成,我们可以逐步分析。首先是第一张异常信息表,有以下字节码:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000340: 00 04(00 00 00 0B 00 10 00 03)00 00 00 0B 00 1F ................
- 00000350: 00 00 00 10 00 1A 00 1F 00 00 00 1F 00 21 00 1F .............!..
- 00000360: 00 00 00 02 00 17 00 00 00 1E 00 07 00 00 00 0F ................
- ...
经过换算,有以下对照信息:
0x0000
(十进制值为0):即start_pc
项的值;0x000B
(十进制值为11):即end_pc
项的值;0x0010
(十进制值为16):即handler_pc
项的值;0x0003
(十进制值为3):即catch_type
项的值,指向了第#3号常量,该常量类型为CONSTANT_Class_info,指向表示全限定名的第#39号常量项,即字符串java/lang/ArithmeticException
。
第二张异常信息表的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000340: 00 04 00 00 00 0B 00 10 00 03(00 00 00 0B 00 1F ................
- 00000350: 00 00)00 10 00 1A 00 1F 00 00 00 1F 00 21 00 1F .............!..
- ...
经过换算,有以下对照信息:
0x0000
(十进制值为0):即start_pc
项的值;0x000B
(十进制值为11):即end_pc
项的值;0x001F
(十进制值为33):即handler_pc
项的值;0x0000
(十进制值为0):即catch_type
项的值,表示任意异常情况都需要转向到handler_pc
处进行处理。
第三张异常信息表的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000350: 00 00(00 10 00 1A 00 1F 00 00)00 1F 00 21 00 1F .............!..
- ...
经过换算,有以下对照信息:
0x0010
(十进制值为16):即start_pc
项的值;0x001A
(十进制值为31):即end_pc
项的值;0x001F
(十进制值为32):即handler_pc
项的值;0x0000
(十进制值为0):即catch_type
项的值,表示任意异常情况都需要转向到handler_pc
处进行处理。
第四张异常信息表的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000350: 00 00 00 10 00 1A 00 1F 00 00(00 1F 00 21 00 1F .............!..
- 00000360: 00 00)00 02 00 17 00 00 00 1E 00 07 00 00 00 0F ................
- ...
经过换算,有以下对照信息:
0x001F
(十进制值为32):即start_pc
项的值;0x0021
(十进制值为33):即end_pc
项的值;0x001F
(十进制值为32):即handler_pc
项的值;0x0000
(十进制值为0):即catch_type
项的值,表示任意异常情况都需要转向到handler_pc
处进行处理。
其实异常表可以通过javap
命令的输出内容快速获取:
- public double divide(int, int);
- flags: ACC_PUBLIC
- Code:
- stack=4, locals=9, args_size=3
- 0: iload_1
- ...
- Exception table:
- from to target type
- 0 11 16 Class java/lang/ArithmeticException
- 0 11 31 any
- 16 26 31 any
- 31 33 31 any
- ...
9.1.2. Code属性的其他属性表
在Code属性的异常表项exception_info
之后,有一个u2类型的值表示attributes_count
,如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- 00000360: 00 00(00 02)00 17 00 00 00 1E 00 07 00 00 00 0F ................
- ...
即0x0002
,十进制值为2,即表示该Code属性表里面还有两个其他的属性表,0x0002
后面就是这两个其他属性的属性表了,同时这个属性表的结构自然也是规定好的。
下面的内容,即是Code属性表里第一张属性表的字节码了:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000360: 00 00 00 02(00 17 00 00 00 1E)00 07 00 00 00 0F ................
- ...
0x0017
:u2类型的attribute_name_index
项,十进制值为23,表示#23号常量字符串LineNumberTable
的索引;0x0000 001E
:u4类型的attribute_length
,表示LineNumberTable这一项属性的长度;十进制值为30,即从下一个字节开始,连续的30个字节都用于描述该属性的其他项信息。
由于这里分析到了LineNumberTable属性,因此接下来会首先介绍LineNumberTable属性表。
9.2. LineNumberTable属性
LineNumberTable属性用于描述Java源码行号与字节码行号(字节码的偏移量)之间的对应关系。它并不是运行时必需的属性,但默认会生成到Class文件之中,可以在javac
命令分别使用-g:none
或-g:lines
选项来取消或要求生成这项信息。如果选择不生成LineNumberTable属性,对程序运行产生的最主要的影响就是当抛出异常时,堆栈中将不会显示出错的行号,并且在调试程序的时候,也无法按照源码行来设置断点。LineNumberTable属性的表结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
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 | 行号表 |
接着上面分析可知,LineNumberTable属性表的字节如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000360: 00 00 00 02(00 17 00 00 00 1E 00 07 00 00 00 0F ................
- 00000370: 00 08 00 10 00 0B 00 15 00 10 00 11 00 12 00 12 ................
- 00000380: 00 17 00 13 00 1A 00 15)00 1C 00 00 00 0A 00 02 ................
- ...
这里需要注意的是,在前面分析过的0x0017
和0x0000 001E
三个字节已经描述了LineNumberTable属性表的attribute_name_index
和attribute_length
项了,接下来的数据需要从line_number_table_length
项开始计算,分析如下:
0x0007
:u2类型的line_number_table_length
项,十进制值为7,表示该LineNumberTable属性含有七项line_number_info
行号表,分别是0x0000 000F
、0x0008 0010
、0x000B 0015
、0x0010 0011
、0x0012 0012
、0x0017 0013
、0x001A 0015
。
七项行号表的意义如下:
0x0000 000F
:字节码第0行对应Java源码第15行;0x0008 0010
:字节码第8行对应Java源码第16行;0x000B 0015
:字节码第11行对应Java源码第21行;0x0010 0011
:字节码第16行对应Java源码第17行;0x0012 0012
:字节码第18行对应Java源码第18行;0x0017 0013
:字节码第23行对应Java源码第19行;0x001A 0015
:字节码第26行对应Java源码第21行;
这个从javap
的反编译内容中也可以获知,注意下面的LineNumberTable项:
- public double divide(int, int);
- flags: ACC_PUBLIC
- Code:
- stack=4, locals=9, args_size=3
- 0: iload_1
- ...
- LineNumberTable:
- line 15: 0
- line 16: 8
- line 21: 11
- line 17: 16
- line 18: 18
- line 19: 23
- line 21: 26
9.3. Exceptions属性
这里的Exceptions属性是在方法表中与Code属性平级的一项属性,与前面讲解的异常表不同。Exceptions属性的作用是列举出方法中可能抛出的受查异常(Checked Excepitons),也就是方法描述时在throws关键字后面列举的异常。它的表结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | number_of_execptions | 1 | 可能抛出的受查异常种类 |
u2 | exception_index_table | number_of_execptions | 受查异常,指向常量池中CONSTANT_Class_info类型的索引,表示该受查异常的类型 |
在ClassFileTest类中我们定义了一个内部类InnerClass,它的add(int number1, int number2)
方法是抛出了IllegalArgumentException异常的,因此我们查看该类的字节码文件,位于ClassFileTest$InnerClass.class文件中:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000220: 1E 00 10 00 00 00 04 00 02 08 09(00 11 00 00 00 ................
- 00000230: 04 00 01 00 03)00 02 00 12 00 00 00 02 00 13 00 ................
- ...
对于以上字节码文件中:
0x0011
:十进制值为17,代表常量池中第#17号常量,即字符串Exceptions
;0x0000 0004
:十进制值为4,代表该异常属性长度,从下一个字节起的连续4个字节都是描述异常属性的字节。0x0001
:十进制值为1,代表可抛出一种受查异常;0x0003
:十进制值为3,代表常量池中第#3号常量,该常量类型为CONSTANT_Class_info,它的类名描述索引指向了第#22号常量,即字符串IllegalArgumentException
。
这里涉及的所有常量如下:
- Constant pool:
- ...
- #3 = Class #22 // java/lang/IllegalArgumentException
- ...
- #17 = Utf8 Exceptions
- ...
- #22 = Utf8 java/lang/IllegalArgumentException
- ...
9.4. LocalVariableTable属性
LocalVariableTable属性用于描述栈帧中局部变量表中的变量与Java源码中定义的变量之间的关系,它也不是运行时必需的属性,但默认会生成到Class文件之中,可以在javac
命令分别使用-g:none
或-g:vars
选项来取消或要求生成这项信息。如果没有生成这项属性,最大的影响就是当其他人引用这个方法时,所有的参数名称都将会丢失,IDE将会使用诸如arg0
、arg1
之类的占位符代替原有的参数名,这对程序运行没有影响,但是会对代码编写带来较大不便,而且在调试期间无法根据参数名称从上下文中获得参数值。LocalVariableTable属性的结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | local_variable_table_length | 1 | 局部变量表数量 |
u2 | local_variable_info | local_variable_table_length | 局部变量信息表,代表栈帧与源码中局部变量的关联 |
其中local_variable_info
也是一张表,它的结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | start_pc | 1 | 局部变量声明周期开始的字节码偏移量 |
u2 | length | 1 | 局部变量作用范围覆盖的长度 |
u2 | name_index | 1 | 局部变量的名称,指向常量池中CONSTANT_Utf8_info类型常量的索引 |
u2 | descriptor_index | 1 | 局部变量的描述符,指向常量池中CONSTANT_Utf8_info类型常量的索引 |
u2 | index | 1 | 局部变量在栈帧局部变量表中Slot的位置 |
start_pc
和length
属性分别代表了这个局部变量的生命周期开始的字节码偏移量及其作用范围覆盖的长度,两者结合起来就是这个局部变量在字节码之中的作用域范围。
name_index
和descriptor_index
都是指向常量池中CONSTANT_Utf8_info型常量的索引,分别代表了局部变量的名称以及这个局部变量的描述符。
index是这个局部变量在栈帧局部变量表中Slot的位置。当这个变量数据类型是64位类型时(double和long),它占用的Slot为index
和index + 1
两个。
在JDK 1.5引入泛型之后,LocalVariableTable属性增加了一个“姐妹属性”:LocalVariableTypeTable,这个新增的属性结构与LocalVariableTable非常相似,仅仅是把记录的字段描述符的descriptor_index
替换成了字段的特征签名(Signature),对于非泛型类型来说,描述符和特征签名能描述的信息是基本一致的,但是泛型引入之后,由于描述符中泛型的参数化类型被擦除掉,描述符就不能准确地描述泛型类型了,因此出现了LocalVariableTypeTable。
9.5. SourceFile属性
SourceFile属性用于记录生成这个Class文件的源码文件名称。这个属性也是可选的,可以分别使用javac
命令的-g:none
或-g:source
选项来关闭或要求生成这项信息。在Java中,对于大多数的类来说,类名和文件名是一致的,但是有一些特殊情况(如内部类)例外。如果不生成这项属性,当抛出异常时,堆栈中将不会显示出错代码所属的文件名。这个属性是一个定长的属性,其结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | sourcefile_index | 1 | 指向常量池中CONSTANT_Utf8_info类型的常量的索引,表示源文件的文件名 |
如在ClassFileTest的字节码文件中,有以下内容:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 000003b0: 00 02 00 22(00 23 00 00 00 02 00 24)00 0B 00 00 ...".#.....$....
- ...
0x0023
:十进制值为35,即表示第#35号常量,为CONSTANT_Utf8_info类型的字符串SourceFile
,代表属性名;0x0000 0002
:十进制值为2,表示后面连续的两个字节表示属性的内容;0x0024
:十进制值为36,即表示第#36号常量,为CONSTANT_Utf8_info类型的字符串ClassFileTest.java
,代表属性值,即源文件的名字。
在javap
命令得到的结果中,也有SourceFile相关的信息:
- $ > javap -verbose ClassFileTest.class
- ...
- public class ClassFileTest<T extends java.lang.Object> extends java.lang.Object implements java.io.Serializable, java.lang.Cloneable
- Signature: #34 // <T:Ljava/lang/Object;>Ljava/lang/Object;Ljava/io/Serializable;Ljava/lang/Cloneable;
- SourceFile: "ClassFileTest.java"
- ...
9.6. ConstantValue属性
ConstantValue属性的作用是通知虚拟机自动为静态变量赋值。只有被static关键字修饰的变量(类变量)才可以使用这项属性。虚拟机对类变量和实例变量赋值的方式和时刻都有所不同。对于非static类型的变量(也就是实例变量)的赋值是在实例构造器<init>
方法中进行的;而对于类变量,则有两种方式可以选择:在类构造器<clinit>
方法中或者使用ConstantValue属性。目前Sun Javac编译器的选择是:如果同时使用final和static来修饰一个变量(按照习惯,这里称“常量”更贴切),并且这个变量的数据类型是基本类型或者String类型的话,就生成ConstantValue属性来进行初始化,如果这个变量没有被final修饰,或者并非基本类型及字符串,则将会选择在<clinit>
方法中进行初始化。ConstantValue属性的结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | constantvalue_index | 1 | 指向常量池中某个常量的索引 |
ConstantValue属性是一个定长属性,其中constantvalue_index
指向了常量池中的某个字面量常量的引用。以我们之前在介绍字段表集合时分析的第二个常量i
为例:它对应的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000290: 00 00 02 00 0F(00 19 00 10 00 11 00 01)00 12 00 ................
- 000002a0: 00 00 02 00 13 00 03 00 01 00 14 00 15 00 01 00 ................
- ...
其中0x0019
、0x0010
和0x0011
都讲解过了,分别表示访问标志位、字段名索引和描述符索引;最后的0x0001
表示该字段有1项属性信息,当时跳过了对这1项属性的讲解,而这1项属性正是ConstantValue属性,我们关注0x0001
后面连续的8个字节:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000290: 00 00 02 00 0F 00 19 00 10 00 11 00 01(00 12 00 ................
- 000002a0: 00 00 02 00 13)00 03 00 01 00 14 00 15 00 01 00 ................
- ...
这些字节的解释如下:
0x0012
:十进制值为18,即attribute_name_index
项,指向了常量池中第#18号常量,即字符串ConstantValue
;0x0000 0002
:十进制为2,即attribute_length
项,表示属性长度为2,即后面连续两个字节为属性的内容;0x0013
:十进制值为19,即constantvalue_index
项,指向了常量池中滴#19号常量,即Integer类型值1
。
很显然,这与我们之前讲解的字段i
一样,这里的ConstantValue描述了字段i
的值。
9.7. InnerClasses属性
InnerClasses属性用于记录内部类与宿主类之间的关联。如果一个类中定义了内部类,那编译器将会为它以及它所包含的内部类生成InnerClasses属性。该属性的结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | number_of_classes | 1 | 记录的内部类的个数 |
inner_classes_info | inner_classes | number_of_classes | 记录内部类的表 |
其中inner_classes_info类型的inner_classes
项是具体用于描述内部类的表,它的结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | inner_class_info_index | 1 | 内部类的索引 |
u4 | outer_class_info_index | 1 | 外部类的索引 |
u2 | inner_name_index | 1 | 内部类名称的索引,如果是匿名内部类,该项为0 |
u2 | inner_class_access_flag | 1 | 内部类的访问标志 |
上面表中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 | 内部类是否为interface |
ACC_ABSTRACT | 0x0400 | 内部类是否为abstract |
ACC_SYNTHETIC | 0x1000 | 内部类是否并非由用户代码产生的 |
ACC_ANNOTATION | 0x2000 | 内部类是否是一个注解 |
ACC_ENUM | 0x4000 | 内部类是否是一个枚举 |
以ClassFileTest类的字节码为例,其中下面的字节码就描述了一个InnerClasses项属性:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 000003b0: 00 02 00 22 00 23 00 00 00 02 00 24(00 0B 00 00 ...".#.....$....
- 000003c0: 00 0A 00 01 00 09 00 05 00 0A 00 00) ............
这些字节的解释如下:
0x000B
:十进制值为11,即attribute_name_index
项,指向了常量池中第#11号常量,即字符串InnerClasses
,表示这是一个InnerClasses属性;0x0000 000A
:十进制值为10,即attribute_length
项,表示后面连续10个字节为描述InnerClasses属性的字节;0x0001
:十进制值为1,即number_of_classes
项,表示内部类的个数;00 09 00 05 00 0A 00 00
:这剩余的8个字节描述了inner_classes_info类型的inner_classes
项。
inner_classes
项的8个字节的解释如下:
0x0009
:十进制值为9,即inner_class_info_index
项,指向了常量池中第#9号常量,为CONSTANT_Class_info类型的常量,该常量表示的就是ClassFileTest$InnerClass类;0x0005
:十进制值为5,即outer_class_info_index
项,指向了常量池中第#5号常量,为CONSTANT_Class_info类型的常量,该常量表示的就是ClassFileTest类;0x000A
:十进制值为10,即inner_name_index
项,指向了常量池中第#10号常量,为CONSTANT_Utf8_info类型的常量,即字符串InnerClass
。0x0000
:十进制值为0,即inner_class_access_flag
项,表示该内部类并没有被修饰,这与我们源码定义的是一致的。
9.8. StackMapTable属性
StackMapTable属性在JDK 1.6发布后增加到了Class文件规范中,它是一个复杂的变长属性,位于Code属性的属性表中。这个属性会在虚拟机类加载的字节码验证阶段被新类型检查验证器(Type Checker)使用,目的在于代替以前比较消耗性能的基于数据流分析的类型推导验证器。
StackMapTable属性中包含零至多个栈映射帧(Stack Map Frames),每个栈映射帧都显式或隐式地代表了一个字节码偏移量,用于表示该执行到该字节码时局部变量表和操作数栈的验证类型。类型检查验证器会通过检查目标方法的局部变量和操作数栈所需要的类型来确定一段字节码指令是否符合逻辑约束。StackMapTable属性的结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | number_of_entries | 1 | 记录stack_map_frame的数量 |
stack_map_frame | stack_map_frame_entries | number_of_entries | 表示一个stack_map_frame |
在前面讲解方法表集合时,其中第三个方法public double divide(int number1, int number2)
的字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000380: 00 17 00 13 00 1A 00 15(00 1C 00 00 00 0A 00 02 ................
- 00000390: 50 07 00 1D 4E 07 00 1E)00 1F 00 00 00 00 00 20 P...N...........
- ...
其中,0x001C
前面的字节全部讲解过了,而从0x001C
开始正是描述StackMapTable属性的字节,这些字节的解释如下:
0x001C
:十进制值为28,即attribute_name_index
项,指向了常量池中第#28号常量,为CONSTANT_Utf8_info类型的常量,即字符串StackMapTable
,表示这是一个StackMapTable属性;0x0000 000A
:十进制值为10,即attribute_length
项,表示后面连续10个字节为描述StackMapTable属性的字节;0x0002
:十进制值为2,即number_of_entries
项,表示stack_map_frame的个数;50 07 00 1D 4E 07 00 1E
:这剩余的8个字节描述了两个stack_map_frame类型的stack_map_frame_entries
项。
两个stack_map_frame类型的stack_map_frame_entries
项的解释如下:
50 07 00 1D
:4E 07 00 1E
:
这部分信息其实在javap
命令输出的内容中也有体现:
- public double divide(int, int);
- flags: ACC_PUBLIC
- Code:
- stack=4, locals=9, args_size=3
- 0: iload_1
- ...
- StackMapTable: number_of_entries = 2
- frame_type = 80 /* same_locals_1_stack_item */
- stack = [ class java/lang/ArithmeticException ]
- frame_type = 78 /* same_locals_1_stack_item */
- stack = [ class java/lang/Throwable ]
- ...
9.9. Deprecated及Synthetic属性
Deprecated和Synthetic两个属性都属于标志类型的布尔属性,只存在有和没有的区别,没有属性值的概念。
Deprecated属性用于表示某个类、字段或者方法,已经被程序作者定为不再推荐使用,它可以通过在代码中使用@deprecated
注解进行设置。
Synthetic属性代表此字段或者方法并不是由Java源码直接产生的,而是由编译器自行添加的,在JDK 1.5之后,标识一个类、字段或者方法是编译器自动产生的,也可以设置它们访问标志中的ACC_SYNTHETIC标志位,其中最典型的例子就是Bridge Method。所有由非用户代码产生的类、方法及字段都应当至少设置Synthetic属性和ACC_SYNTHETIC标志位中的一项,唯一的例外是实例构造器<init>
方法和类构造器<clinit>
方法。
Deprecated和Synthetic属性的结构非常简单,如下表:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
在上表中,attribute_length
项的值必须为0x0000 0000
,因为没有设置其他值的必要。这两个属性一旦存在就会出现在字节码中,否则不会出现。
在前面讲解方法表集合时,其中第三个方法public double divide(int number1, int number2)
由于我们添加了@deprecated
注解,因此在它的字节码中,有如下内容:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000390: 50 07 00 1D 4E 07 00 1E(00 1F 00 00 00 00)00 20 P...N...........
- ...
这些字节的解释如下:
0x001F
:十进制值为31,即attribute_name_index
项,指向了常量池中第#31号常量,为CONSTANT_Utf8_info类型的常量,即字符串Deprecated
,表示这是一个Deprecated属性;0x0000 0000
:十进制值为0,即attribute_length
项。
9.10. Signature属性
Signature属性在JDK 1.5发布后增加到了Class文件规范之中,它是一个可选的定长属性,可以出现于类、属性表和方法表结构的属性表中。在JDK 1.5中大幅增强了Java语言的语法,在此之后,任何类、接口、初始化方法或成员的泛型签名如果包含了类型变量(Type Variables)或参数化类型(Parameterized Types),则Signature属性会为它记录泛型签名信息。之所以要专门使用这样一个属性去记录泛型类型,是因为Java语言的泛型采用的是擦除法实现的伪泛型,在字节码(Code属性)中,泛型信息编译(类型变量、参数化类型)之后都通通被擦除掉。使用擦除法的好处是实现简单(主要修改Javac编译器,虚拟机内部只做了很少的改动)、非常容易实现Backport,运行期也能够节省一些类型所占的内存空间。但坏处是运行期就无法像C#等有真泛型支持的语言那样,将泛型类型与用户定义的普通类型同等对待,例如运行期做反射时无法获得到泛型信息。Signature属性就是为了弥补这个缺陷而增设的,现在Java的反射API能够获取泛型类型,最终的数据来源也就是这个属性。Signature属性的结构如下:
类型 | 名称 | 数量 | 含义 |
---|---|---|---|
u2 | attribute_name_index | 1 | 属性名索引 |
u4 | attribute_length | 1 | 属性长度 |
u2 | signature_index | 1 | 表示类签名、方法类型签名或字段类型签名 |
signature_index
项指向了常量池中一个CONSTANT_Utf8_info类型的常量,表示类签名、方法类型签名或字段类型签名。如果当前的Signature属性是类文件的属性,则这个结构表示类签名,如果当前的Signature属性是方法表的属性,则这个结构表示方法类型签名,如果当前Signature属性是字段表的属性,则这个结构表示字段类型签名。
在我们前面讲解字段表集合时,其中的t
字段是一个泛型字段,它的字节码如下
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000280: 07 00 08 00 02(00 00 00 0C 00 0D 00 01)00 0E 00 ................
- ...
其中0x0000
、0x000C
和0x000D
都讲解过了,表示访问标志、字段名索引和描述符索引,最后的0x0001
是属性计数器,表示字段有1项属性信息,当时跳过了该项属性没有讲解,而这项属性正式Signature属性,字节码如下:
- Order: 01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16
- ----------------------------------------------------------
- Offset: 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
- ...
- 00000280: 07 00 08 00 02 00 00 00 0C 00 0D 00 01(00 0E 00 ................
- 00000290: 00 00 02 00 0F)00 19 00 10 00 11 00 01 00 12 00 ................
这些字节的解释如下:
0x000E
:十进制值为14,即attribute_name_index
项,指向了常量池中第#14号常量,为CONSTANT_Utf8_info类型的常量,即字符串Signature
,表示这是一个Signature属性;0x0000 0002
:十进制值为2,即attribute_length
项,表示后面连续2个字节为描述Signature属性的字节;0x000F
:十进制值为15,即signature_index
项,指向了常量池第#15号CONSTANT_Utf8_info类型的常量,表示字段类型签名TT;
。
9.11. BootstrapMethods属性
BootstrapMethods属性在JDK 1.7发布后增加到了Class文件规范之中,它是一个复杂的变长属性,位于类文件的属性表中。这个属性用于保存invokedynamic指令引用的引导方法限定符。《Java虚拟机规范(Java SE 7版)》规定,如果某个类文件结构的常量池中曾经出现过CONSTANT_InvokeDynamic_info类型的常量,那么这个类文件的属性表中必须存在一个明确的BootstrapMethods属性,另外,即使CONSTANT_InvokeDynamic_info类型的常量在常量池中出现过多次,类文件的属性表中最多也只能有一个BootstrapMethods属性。BootstrapMethods属性与JSR-292中的InvokeDynamic指令和java.lang.Invoke包关系非常密切,要介绍这个属性的作用,必须先弄清楚InovkeDynamic指令的运作原理,这将在后面介绍。
目前的Javac暂时无法生成InvokeDynamic指令和BootstrapMethods属性,必须通过一些非常规的手段才能使用到它们,也许在不久的将来,等JSR-292更加成熟一些,这种状况就会改变。BootstrapMethods属性的结构如下表:
类型 | 名称 | 数量 |
---|---|---|
u2 | attribute_name_index | 1 |
u4 | attribute_length | 1 |
u2 | num_bootstrap_methods | 1 |
bootstrap_method | bootstrap_methods | num_bootstrap_methods |
bootstrap_method类型的bootstrap_methods
项的结构如下:
类型 | 名称 | 数量 |
---|---|---|
u2 | bootstrap_method_ref | 1 |
u4 | num_bootstrap_arguments | 1 |
u2 | bootstrap_arguments | num_bootstrap_arguments |
BootstrapMethods属性中,num_bootstrap_methods项的值给出了bootstrap_methods[]
数组中的引导方法限定符的数量。而bootstrap_methods[]
数组的每个成员包含了一个指向常量池CONSTANT_MethodHandle结构的索引值,它代表了一个引导方法,还包含了这个引导方法静态参数的序列(可能为空)。bootstrap_methods[]
数组中的每个成员必须包含以下3项内容。
bootstrap_method_ref
:bootstrap_method_ref项的值必须是一个对常量池的有效索引。常量池在该索引处的值必须是一个CONSTANT_MethodHandle_info结构。num_bootstrap_arguments
:num_bootstrap_arguments
项的值给出了bootstrap_arguments[]
数组成员的数量。bootstrap_arguments[]
:bootstrap_arguments[]
数组的每个成员必须是一个对常量池的有效索引。常量池在该索引处必须是下列结构之一:CONSTANT_String_info、CONSTANT_Class_info、CONSTANT_Integer_info、CONSTANT_Long_info、CONSTANT_Float_info、CONSTANT_Double_info、CONSTANT_MethodHandle_info或CONSTANT_MethodType_info。