1. 程式人生 > >一個java程式是怎樣執行起來的(1)

一個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檔案中的內容分析完畢!