一、简单介绍
Java编译好后的文件是Class文件,该文件在虚拟机上运行,只有虚拟机能够识别。所以编译后的Class文件不依赖于具体的平台,具有跨平台的特性,但是依赖于虚拟机,而且不需要连接。很多语言,包括Java,Python,都可以编译成Class文件。Class文件与EXE文件相比,比较紧凑,不需要填充和对齐。代码在方法区的Code属性中。
将Person.java代码使用javac Person.java编译:
import java.util.Scanner;
public class Person{
private String name = "xiaoming";
private int age = 23;
public int getAge(){
return age;
}
public Person(String name, int age){
this.name = name;
this.age = age;
}
public void input(){
Scanner scanner = new Scanner(System.in);
}
}
然后使用以下命令查看class文件:
xxd -g 1 Person.class
可以得到:
00000000: ca fe ba be 00 00 00 33 00 26 09 00 08 00 18 0a .......3.&......
00000010: 00 09 00 19 08 00 1a 09 00 08 00 1b 07 00 1c 09 ................
00000020: 00 1d 00 1e 0a 00 05 00 1f 07 00 20 07 00 21 01 ........... ..!.
00000030: 00 04 6e 61 6d 65 01 00 12 4c 6a 61 76 61 2f 6c ..name...Ljava/l
00000040: 61 6e 67 2f 53 74 72 69 6e 67 3b 01 00 03 61 67 ang/String;...ag
00000050: 65 01 00 01 49 01 00 06 67 65 74 41 67 65 01 00 e...I...getAge..
00000060: 03 28 29 49 01 00 04 43 6f 64 65 01 00 0f 4c 69 .()I...Code...Li
00000070: 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 01 00 06 neNumberTable...
00000080: 3c 69 6e 69 74 3e 01 00 16 28 4c 6a 61 76 61 2f <init>...(Ljava/
00000090: 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 49 29 56 01 lang/String;I)V.
000000a0: 00 05 69 6e 70 75 74 01 00 03 28 29 56 01 00 0a ..input...()V...
000000b0: 53 6f 75 72 63 65 46 69 6c 65 01 00 0b 50 65 72 SourceFile...Per
000000c0: 73 6f 6e 2e 6a 61 76 61 0c 00 0c 00 0d 0c 00 12 son.java........
000000d0: 00 15 01 00 08 78 69 61 6f 6d 69 6e 67 0c 00 0a .....xiaoming...
000000e0: 00 0b 01 00 11 6a 61 76 61 2f 75 74 69 6c 2f 53 .....java/util/S
000000f0: 63 61 6e 6e 65 72 07 00 22 0c 00 23 00 24 0c 00 canner.."..#.$..
00000100: 12 00 25 01 00 06 50 65 72 73 6f 6e 01 00 10 6a ..%...Person...j
00000110: 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 01 ava/lang/Object.
00000120: 00 10 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 ..java/lang/Syst
00000130: 65 6d 01 00 02 69 6e 01 00 15 4c 6a 61 76 61 2f em...in...Ljava/
00000140: 69 6f 2f 49 6e 70 75 74 53 74 72 65 61 6d 3b 01 io/InputStream;.
00000150: 00 18 28 4c 6a 61 76 61 2f 69 6f 2f 49 6e 70 75 ..(Ljava/io/Inpu
00000160: 74 53 74 72 65 61 6d 3b 29 56 00 21 00 08 00 09 tStream;)V.!....
00000170: 00 00 00 02 00 02 00 0a 00 0b 00 00 00 02 00 0c ................
00000180: 00 0d 00 00 00 03 00 01 00 0e 00 0f 00 01 00 10 ................
00000190: 00 00 00 1d 00 01 00 01 00 00 00 05 2a b4 00 01 ............*...
000001a0: ac 00 00 00 01 00 11 00 00 00 06 00 01 00 00 00 ................
000001b0: 06 00 01 00 12 00 13 00 01 00 10 00 00 00 47 00 ..............G.
000001c0: 02 00 03 00 00 00 1b 2a b7 00 02 2a 12 03 b5 00 .......*...*....
000001d0: 04 2a 10 17 b5 00 01 2a 2b b5 00 04 2a 1c b5 00 .*.....*+...*...
000001e0: 01 b1 00 00 00 01 00 11 00 00 00 1a 00 06 00 00 ................
000001f0: 00 08 00 04 00 03 00 0a 00 04 00 10 00 09 00 15 ................
00000200: 00 0a 00 1a 00 0b 00 01 00 14 00 15 00 01 00 10 ................
00000210: 00 00 00 28 00 03 00 02 00 00 00 0c bb 00 05 59 ...(...........Y
00000220: b2 00 06 b7 00 07 4c b1 00 00 00 01 00 11 00 00 ......L.........
00000230: 00 0a 00 02 00 00 00 0e 00 0b 00 0f 00 01 00 16 ................
00000240: 00 00 00 02 00 17 ......
Class文件的整体结构如下:
类型 | 名称 | 数量 |
---|---|---|
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_flags | 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 |
method_info | methods | methods_count |
u2 | attributes_count | 1 |
attribute_info | attributes | attributes_count |
下面对整个文件进行解析:
数据结构 | 内容 | 含义 |
---|---|---|
magic | ca fe ba be | 表示class文件,不以后缀作为判断依据 |
minor_version | 00 00 | 次版本号0 |
major_version | 00 33 | 主版本号51,表示JDK1.7.0 |
constant_pool_count | 00 26 | 总共有0x26 - 1 = 37个常量池项,且序号范围是1-37,序号0用于表示没有使用常量池的特殊情况。 |
二、常量池
常量池可以有多种数据类型,很多Class文件的其他项都引用常量池,因而常量池保存了很重要的信息,一般都比较大。总共有37项。常量池的作用:存储变量名和方法名,类全限定名(/分割),方法描述符,类型信息等。
第1项是CONSTANT_Fieldref_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x09 | 表示CONSTANT_Fieldref_info |
CONSTANT_Class_info index | 00 08 | Person |
CONSTANT_NameAndType index | 00 18 | age : I |
第2项是 CONSTANT_Methodref_info,结构与CONSTANT_Fieldref_info类似:
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x0a | 表示CONSTANT_Methodref_info |
CONSTANT_Class_info index | 00 09 | java/lang/Object |
CONSTANT_NameAndType index | 00 19 | “<init>”:()V |
第3项是CONSTANT_String_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x08 | 表示CONSTANT_String_info |
CONSTANT_Utf8_info index | 00 1a | xiaoming |
第4项是CONSTANT_Fieldref_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x09 | 表示CONSTANT_Fieldref_info |
CONSTANT_Class_info index | 00 08 | Person |
CONSTANT_NameAndType index | 00 1b | name:Ljava/lang/String; |
第5项是CONSTANT_Class_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x07 | 表示CONSTANT_Class_info |
CONSTANT_Utf8_info index | 00 1c | java/util/Scanner |
第6项是CONSTANT_Fieldref_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x09 | 表示CONSTANT_Fieldref_info |
CONSTANT_Class_info index | 00 1d | java/lang/System |
CONSTANT_NameAndType index | 00 1e | in:Ljava/io/InputStream; |
第7项是 CONSTANT_Methodref_info,结构与CONSTANT_Fieldref_info类似:
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x0a | 表示CONSTANT_Methodref_info |
CONSTANT_Class_info index | 00 05 | java/util/Scanner |
CONSTANT_NameAndType index | 00 1f | “<init>”:(Ljava/io/InputStream;)V |
第8项是CONSTANT_Class_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x07 | 表示CONSTANT_Class_info |
CONSTANT_Utf8_info index | 00 20 | Person |
第9项是CONSTANT_Class_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x07 | 表示CONSTANT_Class_info |
CONSTANT_Utf8_info index | 00 21 | java/lang/Object |
第10项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 04 | 4个字节 |
byte | 6e 61 6d 65 | name |
第11项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 12 | 18个字节 |
byte | 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b | Ljava/lang/String; |
第12项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 03 | |
byte | 61 67 65 | age |
第13项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 01 | |
byte | 49 | I |
第14项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 06 | |
byte | 67 65 74 41 67 65 | getAge |
第15项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 03 | |
byte | 28 29 49 | ()I |
第16项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 04 | |
byte | 43 6f 64 65 | Code |
第17项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 0f | |
byte | 4c 69 6e 65 4e 75 6d 62 65 72 54 61 62 6c 65 | LineNumberTable |
第18项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 06 | |
byte | 3c 69 6e 69 74 3e | <init> |
第19项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 16 | |
byte | 28 4c 6a 61 76 61 2f 6c 61 6e 67 2f 53 74 72 69 6e 67 3b 49 29 56 | (Ljava/lang/String;I)V |
第20项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 05 | |
byte | 69 6e 70 75 74 | input |
第21项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 03 | |
byte | 28 29 56 | ()V |
第22项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 0a | |
byte | 53 6f 75 72 63 65 46 69 6c 65 | SourceFile |
第23项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 0b | |
byte | 50 65 72 73 6f 6e 2e 6a 61 76 61 | Person.java |
第24项是CONSTANT_NameAndType_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x0c | 表示CONSTANT_NameAndType_info |
name index | 00 0b | age |
descriptor index | 00 0d | I |
第25项是CONSTANT_NameAndType_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x0c | 表示CONSTANT_NameAndType_info |
name index | 00 12 | <init> |
descriptor index | 00 15 | ()V |
第26项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 08 | |
byte | 78 69 61 6f 6d 69 6e 67 | xiaoming |
第27项是CONSTANT_NameAndType_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x0c | 表示CONSTANT_NameAndType_info |
name index | 00 0a | name |
descriptor index | 00 0b | Ljava/lang/String; |
第28项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 11 | |
byte | 6a 61 76 61 2f 75 74 69 6c 2f 53 63 61 6e 6e 65 72 | java/util/Scanner |
第29项是CONSTANT_Class_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x07 | 表示CONSTANT_Class_info |
class name index | 00 22 | java/lang/System |
第30项是CONSTANT_NameAndType_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x0c | 表示CONSTANT_NameAndType_info |
name index | 00 23 | in |
descriptor index | 00 24 | Ljava/io/InputStream; |
第31项是CONSTANT_NameAndType_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x0c | 表示CONSTANT_NameAndType_info |
name index | 00 12 | <init> |
descriptor index | 00 25 | (Ljava/io/InputStream;)V |
第32项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 06 | |
byte | 50 65 72 73 6f 6e | Person |
第33项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 10 | |
byte | 6a 61 76 61 2f 6c 61 6e 67 2f 4f 62 6a 65 63 74 | java/lang/Object |
第34项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 10 | |
byte | 6a 61 76 61 2f 6c 61 6e 67 2f 53 79 73 74 65 6d | java/lang/System |
第35项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 02 | |
byte | 69 6e | in |
第36项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 15 | |
byte | 4c 6a 61 76 61 2f 69 6f 2f 49 6e 70 75 74 53 74 72 65 61 6d 3b | Ljava/io/InputStream; |
第37项是CONSTANT_Utf8_info
数据结构 | 内容 | 含义 |
---|---|---|
tag | 0x01 | 表示CONSTANT_Utf8_info |
length | 00 18 | |
byte | 28 4c 6a 61 76 61 2f 69 6f 2f 49 6e 70 75 74 53 74 72 65 61 6d 3b 29 56 | (Ljava/io/InputStream;)V |
由此可以得到常量池中主要数据类型的关系:
三、其他部分
数据结构 | 内容 | 含义 |
---|---|---|
access_flags | 00 21 | 本类为public |
this_class | 00 08 | 常量池索引。Person |
super_class | 00 09 | java/lang/Object |
interfaces_count | 00 00 | |
interfaces | 无 | |
fields_count | 00 02 |
fields :
- 第一项name java.lang.String:
数据结构 | 内容 | 含义 |
---|---|---|
access_flags | 0002 | private |
name_index | 00 0a | name |
descriptor index | 00 0b | Ljava/lang/String; |
attribute_counts | 00 00 | 零项 |
attribute_info | 无 |
- 第二项age I:
数据结构 | 内容 | 含义 |
---|---|---|
access_flags | 0002 | private |
name_index | 00 0c | age |
descriptor index | 00 0d | I |
attribute_counts | 00 00 | 零项 |
attribute_info | 无 |
methods_count : 00 03。method的结构与field类似。
- 第一项getAge().I:
数据结构 | 内容 | 含义 |
---|---|---|
access_flags | 0001 | public |
name_index | 00 0e | getAge |
descriptor index | 00 0f | ()I |
attribute_counts | 00 01 | 一项 |
attribute_info | 一项 |
attribute_info的信息如下:
数据结构 | 内容 | 含义 |
---|---|---|
attribute_name_index | 00 10 | 属性为Code |
attribute_length | 00 00 00 1d | 属性长度为29,表示下面有29个字节 |
max_stack | 00 01 | |
max_locals | 00 01 | |
code_length | 00 00 00 05 | |
code | 2a b4 00 01 ac | |
exception_table_length | 00 00 | |
attributes_count | 00 01 | |
attribute_name_index | 00 11 | 索引项17为LineNumberTable |
attribute_length | 00 00 00 06 | 表示下面有6个字节 |
line_number_table_length | 00 01 | |
line_number_table | 00 00 00 06 | 前两个字节为字节码行号0,后两个字节码为Java源码行号6 |
- 第二项public Person(String name, int age):
数据结构 | 内容 | 含义 |
---|---|---|
access_flags | 0001 | public |
name_index | 00 12 | <init> |
descriptor index | 00 13 | (Ljava/lang/String;I)V |
attribute_counts | 00 01 | 一项 |
attribute_info | 一项 |
属性内容为
数据结构 | 内容 | 含义 |
---|---|---|
attribute_name_index | 00 10 | 属性为Code |
attribute_length | 00 00 00 47 | 71 |
max_stack | 00 02 | |
max_locals | 00 03 | |
code_length | 00 00 00 1b | 27 |
code | 2a b7 00 02 2a 12 03 b5 00 04 2a 10 17 b5 00 01 2a 2b b5 00 04 2a 1c b5 00 01 b1 | |
exception_table_length | 00 00 | |
attributes_count | 00 01 | |
attribute_name_index | 00 11 | 属性为LineNumberTable |
attribute_length | 00 00 00 1a | 26个字节 |
line_number_table_length | 00 06 | |
line_number_table | 00 00 00 08 00 04 00 03 00 0a 00 04 00 10 00 09 00 15 00 0a 00 1a 00 0b | 共有6项,每一项4个字节。前两个字节为字节码行号,后两个字节为Java源码行号 |
- 第三项public void input():
数据结构 | 内容 | 含义 |
---|---|---|
access_flags | 0001 | public |
name_index | 00 14 | input |
descriptor index | 00 15 | ()V |
attribute_counts | 00 01 | 一项 |
attribute_info | 一项 |
属性内容为
数据结构 | 内容 | 含义 |
---|---|---|
attribute_name_index | 00 10 | 属性为Code |
attribute_length | 00 00 00 28 | 40 |
max_stack | 00 03 | |
max_locals | 00 02 | |
code_length | 00 00 00 0c | 12 |
code | bb 00 05 59 b2 00 06 b7 00 07 4c b1 | |
exception_table_length | 00 00 | |
attributes_count | 00 01 | |
attribute_name_index | 00 11 | |
attribute_length | 00 00 00 0a | |
line_number_table_length | 00 02 | 有2项line_number_table |
line_number_info | 00 00 00 0e 00 0b 00 0f | 前两个字节为字节码行号,后两个字节为Java源码行号 |
从上述定义可以看出,常量池中的Fieldref和Methodref只是起引用作用。还需要在全局Fields和Methods中定义其访问属性,对于方法还要有Code属性来存储字节码指令。
最后,本类SourceFile属性如下:
数据结构 | 内容 | 含义 |
---|---|---|
attributes_count | 00 01 | 本类只有一项属性 |
attribute_name_index | 00 16 | SourceFile |
attribute_length | 00 00 00 02 | |
sourcefile_index | 00 17 | Person.java |
四、使用javap命令反编译Class文件
具体命令如下:
javap -verbose Person.class
得到以下输出,它包含了Class文件的详细信息。
Classfile /home/jessin/Documents/Program/Java/Person.class
Last modified 2017-7-3; size 582 bytes
MD5 checksum 61bc2d8c4a18440e51dddf16acc70dfe
Compiled from "Person.java"
public class Person
SourceFile: "Person.java"
minor version: 0
major version: 51
flags: ACC_PUBLIC, ACC_SUPER
Constant pool:
#1 = Fieldref #8.#24 // Person.age:I
#2 = Methodref #9.#25 // java/lang/Object."<init>":()V
#3 = String #26 // xiaoming
#4 = Fieldref #8.#27 // Person.name:Ljava/lang/String;
#5 = Class #28 // java/util/Scanner
#6 = Fieldref #29.#30 // java/lang/System.in:Ljava/io/InputStream;
#7 = Methodref #5.#31 // java/util/Scanner."<init>":(Ljava/io/InputStream;)V
#8 = Class #32 // Person
#9 = Class #33 // java/lang/Object
#10 = Utf8 name
#11 = Utf8 Ljava/lang/String;
#12 = Utf8 age
#13 = Utf8 I
#14 = Utf8 getAge
#15 = Utf8 ()I
#16 = Utf8 Code
#17 = Utf8 LineNumberTable
#18 = Utf8 <init>
#19 = Utf8 (Ljava/lang/String;I)V
#20 = Utf8 input
#21 = Utf8 ()V
#22 = Utf8 SourceFile
#23 = Utf8 Person.java
#24 = NameAndType #12:#13 // age:I
#25 = NameAndType #18:#21 // "<init>":()V
#26 = Utf8 xiaoming
#27 = NameAndType #10:#11 // name:Ljava/lang/String;
#28 = Utf8 java/util/Scanner
#29 = Class #34 // java/lang/System
#30 = NameAndType #35:#36 // in:Ljava/io/InputStream;
#31 = NameAndType #18:#37 // "<init>":(Ljava/io/InputStream;)V
#32 = Utf8 Person
#33 = Utf8 java/lang/Object
#34 = Utf8 java/lang/System
#35 = Utf8 in
#36 = Utf8 Ljava/io/InputStream;
#37 = Utf8 (Ljava/io/InputStream;)V
{
public int getAge();
flags: ACC_PUBLIC
Code:
stack=1, locals=1, args_size=1
0: aload_0
1: getfield #1 // Field age:I
4: ireturn
LineNumberTable:
line 6: 0
public Person(java.lang.String, int);
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=3
0: aload_0
1: invokespecial #2 // Method java/lang/Object."<init>":()V
4: aload_0
5: ldc #3 // String xiaoming
7: putfield #4 // Field name:Ljava/lang/String;
10: aload_0
11: bipush 23
13: putfield #1 // Field age:I
16: aload_0
17: aload_1
18: putfield #4 // Field name:Ljava/lang/String;
21: aload_0
22: iload_2
23: putfield #1 // Field age:I
26: return
LineNumberTable:
line 8: 0
line 3: 4
line 4: 10
line 9: 16
line 10: 21
line 11: 26
public void input();
flags: ACC_PUBLIC
Code:
stack=3, locals=2, args_size=1
0: new #5 // class java/util/Scanner
3: dup
4: getstatic #6 // Field java/lang/System.in:Ljava/io/InputStream;
7: invokespecial #7 // Method java/util/Scanner."<init>":(Ljava/io/InputStream;)V
10: astore_1
11: return
LineNumberTable:
line 14: 0
line 15: 11
}
参考文献:《深入理解Java虚拟机》周志明