《Java 底層原理》Java 字節碼詳解

前言

我們在開發中會遇到一些Java的執行超出我們的想象,但是又不知道他爲什麼會這樣執行,這個時候我們就需要能夠知道他編譯後Class文件是什麼樣子的,並且理解字節碼的含義。

Java字節碼的原理

進制

class文件就是字節碼文件,直接是打不開,打開也是亂碼,需要解析才能看明白裏面的內容。

現在存在很多語言都是允許在Jvm上,比如Kotlin。 他們其實就是通過編譯也編譯成Jvm認識的.class 文件即可。

大端和小端

大端模式:是指數據的高字節保存在內存的低地址中,而數據的低字節保存在內存的高地址中,這樣的存儲模式有點兒類似於把數據當作字符串順序處理:

地址由小向大增加,而數據從高位往低位放;這和我們的閱讀習慣一致。

小端模式:是指數據的高字節保存在內存的高地址中,而數據的低字節保存在內存的低地址中,這種存儲模式將地址的高低和數據位權有效地結合起來,

高地址部分權值高,低地址部分權值低。

字節碼文件內容組成結構

Java class文件的內容結構

下面解析一下一個不太好理解的結構。

1. 魔數:用於判斷這個文件是不是class合格的文件。

2. 次版本號和主板本號:主板本號加次版本號用於判斷這個class文件是否能被這個版本的Jvm 解析, 比如jdk8的class就不能被java7版本的Jvm解析。

3. 常量池個數,最小是1,真實的常量池個數是2個字節計算出來的數量 - 1。

下面是常量池的字節碼結構(u1 就是一個字節,u2 就是兩個字節,類推):

4. 類的訪問控制權限

補充 :Acc_static                               0x0008         static 修飾
舉個案例:
String[] 數組通過字節碼錶示是[Ljava/lang/String;
5. 類的成員變量字節碼格式:
filed_info: {
    u2 access_flags;    -- 屬性的訪問類型和修飾符
    u2 name_index;      -- 成員變量的名字,指向常量池的地址
    u2 descriptor_index;
    u2 attributes_count;
    attribute_info attributes[attributes_count];
}

6. 方法的字節碼格式:

method_info {
    u2 access_flags;    -- 方法的訪問屬性和修飾符
    u2 name_index;      -- 方法的名字,指向常量池的地址
    u2 descriptor_index;   -- 描述符字符串,指向常量池地址,mian方法的描述符([java/lang/String;)v
    u2 attributes_count;  -- 對應下面的code_attribute
    attribute_info attributes[attributes_count];
}

一般一個類除了你定義的方法外還會存在兩個方法,clinit 和 init 處理你的靜態代碼和默認構造函數。

方法字節碼:還包含:

code_attribute { -- Code_attribute包含某個方法、實例初始化方法、類或接口初始化方法的Java虛擬機指令及相關輔助信息
    u2  attribute_name_index; -- 指向常量池,名稱
    u4  attribute_length; -- 後面全部的總長度
    u2  max_stack; -- 用來給出當前方法的操作數棧在方法執行的任何時間點的最大深度
    u2  max_locals;  -- 用來給出分配在當前方法引用的局部變量表中的局部變量個數
    u4  code_length; --給出當前方法code[]數組的字節數
    u1  code[code_length]; -- 給出了實現當前方法的Java虛擬機代碼的實際字節內容(這些數字代碼實際對應一些Java虛擬機的指令)
    u2  exception_table_lentgh;  -- 異常的信息個數
    {
        u2  start_pc; -- 這兩項的值表明了異常處理器在code[]中的有效範圍,即異常處理器x應滿足:start_pc≤x≤end_pc
        u2  end_pc; -- start_pc必須在code[]中取值,end_pc要麼在code[]中取值,要麼等於code_length的值
        u2  handler_pc; -- 表示一個異常處理器的起點
        u2  catch_type; -- 表示當前異常處理器需要捕捉的異常類型。爲0,則都調用該異常處理器,可用來實現finally。
    } exception_table[exception_table_lentgh]; 
    u2  attribute_count; -- 表示該方法的其它附加屬性,
    attribute_info  attributes[attributes_count]; -- LineNumberTable、LocalVariableTable
}

Java方法所在行信息:

LineNumberTable_attribute{ --被調試器用來確定源文件中由給定的行號所表示的內容,對應於Java虛擬機code[] 數組的哪部分
    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];
}

局部變量表信息:

LocalVariableTable_attribute{
    u2  attribute_name_index;
    u4  attribute_length;
    u2  local_variable_table_length;
    {
        u2 start_pc;
        u2 length;
        u2 name_index;
        u2 descriptor_index; --用來表示源程序中局部變量類型的字段描述符
        u2 index;
    } local_variable_table[local_variable_table_length];
}

7.類屬性字節碼格式:

attribute_info: {
    u2 attribute_name_index;
    u1 attribute_length;
    u1 info[attribute_length];
}

字節碼文件解析

我們一起看一下Java編譯後的class文件:

這個是按照16進制顯示的,沒有按照任何編碼的方式進行解析過的原信息。

我們按照上面字節碼文件內容組成結構,來解析一下這個字節碼文件

魔數:cafe babe 就表示這個是class 文件,Jvm才識別。

次版本號:0000

主版本號:0034

常量池數量:0021 就是2*16 + 1 33個常量池,但是需要減一,得到32個常量池。

常量池信息:前一個字節是tag 表示常量池的類型: oa 等於10 從常量池結構圖可以找到時 constent_Methodref_info 這個類型,後面讀取4個字節 00 0600 12;

                      00 06 表示constent_class_info的索引項;00 12 表示constent_nameAndType_info 名稱和類型描述符的索引項,一次解析32個常量池。

                      特殊說明: 01類型的常量池,需要根據length 的長度動態解析。 比如 tag : 01  length : 0006   字符串:3c69 6e69 743e。

類的控制訪問權限:0021 表示:0020加0001組合,說明是...和public 。

類名:0005 間接引用常量池第5個常量池

父類名:0006 間接引用常量池第6個常量池

接口數量:0001 實現一個接口。

接口數組:0007 指向常量池 第7個常量池,如果接口數量爲零則不出現。

成員變量數量:0000 表示沒有成員變量。

成員變量數組:如果成員變量數量爲零則不佔用字節。

方法數量:0002  兩個方法,

方法數組:0001:修飾詞pubilc;0008:方法名,指向常量池;0009 :描述符,指向常量池 ;0001:code_attributes的數量;

                  開始解析code_attribute 000a:code_attribute名稱,指向常量池;0000 002f :attribute的長度47;0001:max_stack 操作數棧;0001:max_locals 局部變量個數;0000 0005 :code的長度; 2ab7 0001 b1 :code的內容就是操作虛擬機的指令信息; 00 00 :異常信息沒有;00 02:表是其他附加信息有兩個。

                 開始解析LineNumberTable_attribute :00 0b:指向常量池,就是指的lineNumberTable;00 0000 06:指的是這個信息的長度;00 01 :line_number_table_length;

                 00 00:start_pc ;  00 03 :Java這個方法的代碼行號。

                 開始解析LocalVariableTable_attribute  00 0c:額外信息的名字,指向常量池;00 0000 0c:該信息長度;00 01:variable_table 信息的長度;00 00:start_pc;

                 00 05:長度;000d:指向常量池,局部變量描述符this;00 0e:指向常量池,類信息描述符;

解析方法字節碼的過程中init 方法解析完成後,中間出現0000 無法解析,我猜是clinit 方法的解析,但是因爲我們寫所以使用0000 表示了。

類屬性數量:

類屬性數組:0001:第一次屬性;  0010:指向常量池;  0000:不知道; 0002:第二個屬性; 0011:指向常量池。

到此字節碼文件全部解析完畢, 中間有一點瑕疵,後續學習中改進。 

總結

字節碼學習,讓我們瞭解Java底層的實現有巨大的幫助。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章