首先编写一测试程序
public class Test {
public static void main(String[] args){
System.out.println("test");
}
}
执行javac Test.java 得到Test.class文件(编译过程有点复杂,这里先不看)
执行java Test,控制台输出"test",想要弄清楚java程序是怎么运行起来首先得了解清楚class文件
看下Test.class里究竟是什么东西,class文件的内容如下:
上图中都是以16进制表示,接下来挨个分析其中的内容表示什么意思。class文件中存储的数据可以参考下表:
类型 | 名称 | 数量 |
u4 | magic | 1 |
u2 | class_minor_version | 1 |
u2 | class_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 | attribute_count | 1 |
attribute_info | attributes | attributes_count |
1、magic 魔数
CA FE BA BE
魔数,确定该文件是否是虚拟机可以接受的文件
2、class文件版本信息
00 00 00 33
class文件的版本号,51表示jdk1.7.0
3、常量池
3.1常量池入口
00 1D
常量池数量为29-1=28,每个类只有一个常量池
常量池中放了字符串,常量值,类名称,字段名,方法名等,反编译下Test.class,看看常量池中存放了哪些东西
Constant pool:
#1 = Methodref #6.#15 // java/lang/Object."":()V
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
#3 = String #18 // test
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
#5 = Class #21 // Test
#6 = Class #22 // java/lang/Object
#7 = Utf8
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Test.java
#15 = NameAndType #7:#8 // "":()V
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 test
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Test
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
常量池中的项目类型有:
CONSTANT_NameAndType_info tag 标志位为12,字段和方法的名称以及类型的符号引用
3.2常量池内容
接上,继续分析class中的内容,参照 jvm官方文档 ,看下常量池中究竟是什么东西
常量池1-----0A 00 06 00 0F //
1,0A---tag为10,表示第一个常量类型为CONSTANT_Methodref,参照官方文档,CONSTANT_Methodref的结构为
CONSTANT_Methodref_info { u1 tag; u2 class_index; u2 name_and_type_index; }所以后面跟了4个字节
2,00 06---声明当前方法类描述符索引值为6 // java/lang/Object
3,00 0F---当前方法的名称和类型索引值为15 // "
所以,结合上文中反编译出的内容来看,这几个16进制翻译过来正好是
#1 = Methodref #6.#15 // java/lang/Object."":()V
常量池2----09 00 10 00 11
1,09---tag为9,类型为CONSTANT_Fieldref
2,00 10---声明当前方法类描述符索引值为16 // java/lang/System
3,00 11---字段描述符的名称和类型索引值为17 // out:Ljava/io/PrintStream;
这几个16进制翻译过来正好是
#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
常量池3---08 00 12
1,08---tag为8,类型为CONSTANT_String,根据官方文档,其结构为
CONSTANT_String_info { u1 tag; u2 string_index; }所以后面跟了两个字节
2,00 12---声明当前String值所在的索引值为18
当前16进制翻译过来,表示
#3 = String #18 // test
常量池4---0A 00 13 00 14,对照着上面的分析,
#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V
常量池5---07 00 15
1,07---tag为7,类型为CONSTANT_Class,根据官方文档,其结构为
CONSTANT_Class_info { u1 tag; u2 name_index; }2,00 15----当前类名称的索引值为21
#5 = Class #21 // Test
常量池6---07 00 16,对照上面的分析
#6 = Class #22 // java/lang/Object
常量池7---01 00 06 3C 69 6E 69 74 3E
1,01---tag为1,类型为CONSTANT_Utf8,根据官方文档
CONSTANT_Utf8_info { u1 tag; u2 length; u1 bytes[length]; }2,06---表示字符串的长度为6
3,3C 69 6E 69 74 3E ---字符串
#7 = Utf8
常量池8---01 00 03 28 29 56
常量池9---01 00 04 43 6F 64 65
常量池10---01 00 0F 4C 69 6E 65 4E 75 6D 62 65 72 54 61 62 6C 65
常量池11---01 00 04 6D 61 69 6E
常量池12---01 00 16 28 5B 4C 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
常量池13---01 00 0A 53 6F 75 72 63 65 46 69 6C 65
常量池14---01 00 09 54 65 73 74 2E 6A 61 76 61
#8 = Utf8 ()V
#9 = Utf8 Code
#10 = Utf8 LineNumberTable
#11 = Utf8 main
#12 = Utf8 ([Ljava/lang/String;)V
#13 = Utf8 SourceFile
#14 = Utf8 Test.java
常量池15---0C 00 07 00 08
1,0C---tag为11,类型为CONSTANT_NameAndType,参照jvm官方文档,其结构为
CONSTANT_NameAndType_info { u1 tag; u2 name_index; u2 descriptor_index; }2,00 07---该字段或方法名称常量索引值为7,即
#7 = Utf8
3,00 08---该字段或方法描述符常量索引值为8 ,即
#8 = Utf8 ()V
常量池16---07 00 17
常量池17---0C 00 18 00 19
常量池18---01 00 04 74 65 73 74
常量池19---07 00 1A
常量池20---0C 00 1B 00 1C
常量池21---01 00 04 54 65 73 74
常量池22---01 00 10 6A 61 76 61 2F 6C 61 6E 67 2F 4F 62 6A 65 63 74
常量池23---01 00 10 6A 61 76 6A 2F 6C 61 6E 67 2F 53 79 73 74 65 6D
常量池24---01 00 03 6F 75 74
常量池25---01 00 15 4C 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D 3B
常量池26---01 00 13 6A 61 76 61 2F 69 6F 2F 50 72 69 6E 74 53 74 72 65 61 6D
常量池27---01 00 07 70 72 69 6E 74 6C 6E
常量池28---01 00 15 28 4 6A 61 76 61 2F 6C 61 6E 67 2F 53 74 72 69 6E 67 3B 29 56
#16 = Class #23 // java/lang/System
#17 = NameAndType #24:#25 // out:Ljava/io/PrintStream;
#18 = Utf8 test
#19 = Class #26 // java/io/PrintStream
#20 = NameAndType #27:#28 // println:(Ljava/lang/String;)V
#21 = Utf8 Test
#22 = Utf8 java/lang/Object
#23 = Utf8 java/lang/System
#24 = Utf8 out
#25 = Utf8 Ljava/io/PrintStream;
#26 = Utf8 java/io/PrintStream
#27 = Utf8 println
#28 = Utf8 (Ljava/lang/String;)V
到此常量池结束
4、访问标志access_flags
00 21----Test类的访问标志,参照官方文档,访问标志有
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 | Declared public ; may be accessed from outside its package. |
ACC_FINAL |
0x0010 | Declared final ; no subclasses allowed. |
ACC_SUPER |
0x0020 | Treat superclass methods specially when invoked by the invokespecial instruction. |
ACC_INTERFACE |
0x0200 | Is an interface, not a class. |
ACC_ABSTRACT |
0x0400 | Declared abstract ; must not be instantiated. |
ACC_SYNTHETIC |
0x1000 | Declared synthetic; not present in the source code. |
ACC_ANNOTATION |
0x2000 | Declared as an annotation type. |
ACC_ENUM |
0x4000 | Declared as an enum type. |
0x0021 = 0x0020|0x0001,即ACC_PUBLIC和ACC_SUPER为真,ACC_PUBLIC好理解,ACC_SUPER这是什么鬼,翻看官方文档,原文如下:
The ACC_SUPER
flag indicates which of two alternative semantics is to be expressed by the invokespecial instruction (§invokespecial) if it appears in this class. Compilers to the instruction set of the Java Virtual Machine should set the ACC_SUPER
flag.
The ACC_SUPER
flag exists for backward compatibility with code compiled by older compilers for the Java programming language. In Oracle’s JDK prior to release 1.0.2, the compiler generated ClassFile
access_flags
in which the flag now representing ACC_SUPER
had no assigned meaning, and Oracle's Java Virtual Machine implementation ignored the flag if it was set.
为了兼容之前的jdk版本,在jdk1.0.2之后这个编译出来的为真
5,类索引,父类索引,接口索引
接下来就是类索引,父类索引,接口索引
00 05------类索引值为#5
#5 = Class #21 // Test
00 06-----父类索引值为#6
#6 = Class #22 // java/lang/Object
00 00----类没有实现接口,接口数为0,所以后面没有接口信息
6、字段
00 00----当前类有0个字段
7、方法,指令
00 02----当前类有两个方法,参照官方文档,方法的结构如下:
method_info { u2 access_flags; u2 name_index; u2 descriptor_index; u2 attributes_count; attribute_info attributes[attributes_count]; }方法1:00 01 00 07 00 08 00 01
----00 01:access_flags=0x0001=ACC_PUBLIC,方法的访问标志如下表:
Flag Name | Value | Interpretation |
---|---|---|
ACC_PUBLIC |
0x0001 | Declared public ; may be accessed from outside its package. |
ACC_PRIVATE |
0x0002 | Declared private ; accessible only within the defining class. |
ACC_PROTECTED |
0x0004 | Declared protected ; may be accessed within subclasses. |
ACC_STATIC |
0x0008 | Declared static . |
ACC_FINAL |
0x0010 | Declared final ; must not be overridden (§5.4.5). |
ACC_SYNCHRONIZED |
0x0020 | Declared synchronized ; invocation is wrapped by a monitor use. |
ACC_BRIDGE |
0x0040 | A bridge method, generated by the compiler. |
ACC_VARARGS |
0x0080 | Declared with variable number of arguments. |
ACC_NATIVE |
0x0100 | Declared native ; implemented in a language other than Java. |
ACC_ABSTRACT |
0x0400 | Declared abstract ; no implementation is provided. |
ACC_STRICT |
0x0800 | Declared strictfp ; floating-point mode is FP-strict. |
ACC_SYNTHETIC |
0x1000 | Declared synthetic; not present in the source code. |
---00 07:name_index=#7----->#7 = Utf8
---00 08:descriptor_index=#8------>#8 = Utf8 ()V
---00 01:attributes_count=1,所以紧随其后就是attribute_info部分,根据官方文档,其结构如下:
Code_attribute { u2 attribute_name_index; u4 attribute_length; u2 max_stack; u2 max_locals; u4 code_length; u1 code[code_length]; u2 exception_table_length; { u2 start_pc; u2 end_pc; u2 handler_pc; u2 catch_type; } exception_table[exception_table_length]; u2 attributes_count; attribute_info attributes[attributes_count]; }00 09 00 00 00 1D 00 01 00 01 00 00 00 05 //非指令部分
---00 09:attribute_name_index=#9---------->#9 = Utf8 Code
---00 00 00 1D:attribute_length=29,所以整个属性表的长度为29+6=35,见官方文档说明:The value of the attribute_length
item indicates the length of the attribute, excluding the initial six bytes.
---00 01:max_stack=1
---00 01:max_locals=1
---00 00 00 05:code_length=5
紧接着就是方法1的指令部分:
2A B7 00 01 B1
---2A:aload_0 ,
---B7 00 01 ->invokespecial #1,调用超类构造方法
---B1--->return
方法1的Exception:
00 00:方法没有throw异常
方法1的attribute count:
00 01://方法1最后有一个属性块,其结构如下:
LineNumberTable_attribute { u2 attribute_name_index; u4 attribute_length; u2 line_number_table_length; { u2 start_pc; u2 line_number; } line_number_table[line_number_table_length]; }00 0A 00 00 00 06 00 01
00 00 00 01
---00 0A:attribute_name_index=#10---->#10 = Utf8 LineNumberTable
---00 00 00 06:attribute_lenght=6
---00 01:line_number_table_length=1,表示这个LineNumberTable中有一条记录
---00 00 00 01:表示Test.java的第一行代码对应指令0--->0: aload_0
方法2:00 09 00 0B 00 0C 00 01
---00 09:access_flags=0x0008|0x0001=ACC_STATIC|ACC_PUBLIC
---00 0B:name_index=#11------>#11 = Utf8 main
---00 0C:descriptor_index=#12----->#12 = Utf8 ([Ljava/lang/String;)V
---00 01:arrtibutes_count=1,紧接着是attribute_info
方法2的code,非指令部分:
00 09 00 00 00 25 00 02 00 01 00 00 00 09
---00 09:attribute_name_index=#9----->#9 = Utf8 Code
---00 00 00 25:attribute_length=37,所以整个属性表的长度为43
---00 02:max_stack=2
---00 01:max_locals=1
---00 00 00 09:code_length=17
方法2的code,指令部分
B2 00 02----->getstatic #2:获取指定类的静态域,并且压入到栈顶,#2表示#2 = Fieldref #16.#17 // java/lang/System.out:Ljava/io/PrintStream;
12 03--->ldc #3,将“test”常量值从常量池中压入到栈顶
B6 00 04---->invokervirtual #4,调用实例方法,#4 = Methodref #19.#20 // java/io/PrintStream.println:(Ljava/lang/String;)V,即println方法
B1---->return
方法2的Exception:
00 00 ----->没有定义throw
方法2的attribute_count:
00 01 //方法最后有个属性
方法2的LineNumberTable:
00 0A 00 00 00 0A 00 02
----00 0A:attribute_name_index=#10------>#10 = Utf8 LineNumberTable
----00 00 00 0A:attribute_length=10
----00 02:line_number_table_lenght=2,表示lineNumberTable中有2条记录
00 00 00 04:Test.java第4行对应指令0 --->getstatic #2
00 08 00 05:Test.java第5行对应指令8----->8: return
8.sourceFile属性
00 01:当前class文件包含1个attribute_info
00 0D 00 00 00 02 00 0E
---00 0D:attribute_name_index=#13---->#13 = Utf8 SourceFile
---00 00 00 02:attribute_length=2
---00 0E:sourcefile_index=#14---->#14 = Utf8 Test.java
至此,class文件中的内容分析完毕!