一個java程式是怎樣執行起來的(1)
首先編寫一測試程式
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."<init>":()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 <init> #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 // "<init>":()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_Utf8_info tag標誌位為1, UTF-8編碼的字串,比如類或介面的全限定名,引數名等
CONSTANT_Integer_info tag標誌位為3, int整型字面量
CONSTANT_Float_info tag標誌位為4, float浮點型字面量
CONSTANT_Long_info tag標誌位為5, long長整形字面量
CONSTANT_Double_info tag標誌位為6, double雙精度字面量
CONSTANT_Class_info tag標誌位為7, 類或介面的符號引用,指向包含字串字面值的CONSTANT_Utf8表
CONSTANT_String_info tag標誌位為8,字串型別的字面量,指向包含字串字面值的CONSTANT_Utf8表
CONSTANT_Fieldref_info tag標誌位為9, 欄位的符號引用,指向包含該欄位所屬類名的CONSTANT_Utf8表
CONSTANT_Methodref_info tag標誌位為10,類中方法的符號引用,指向包含該方法所屬型別的CONSTANT_Utf8表
CONSTANT_InterfaceMethodref_info tag標誌位為11, 介面中方法的符號引用
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 // "<init>":()V
所以,結合上文中反編譯出的內容來看,這幾個16進位制翻譯過來正好是
#1 = Methodref #6.#15 // java/lang/Object."<init>":()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 ---字串<init>
#7 = Utf8 <init>
常量池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 <init>
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 <init>,可以看出該方法為建構函式
---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檔案中的內容分析完畢!