【JVM】或許,這就是二進位制Class吧
水稻:看你研究盯著這個檔案一天了,什麼玩意讓人心馳神往
菜瓜:前幾天意外得到一本武功祕籍《jvms8》,看起來就情不自禁
水稻:這不是Java虛擬機器器的說明檔案嗎<PS:投來驚嚇的目光>
菜瓜:是的,在研究第四章-The class File Format. 講的是class檔案結構。以前模糊的知道我們寫的java程式碼是以二進位制位元組碼載入到虛擬機器器然後執行的,但是沒有見識過
水稻:有什麼收穫,分享一下啊
菜瓜:只是在研究,可以一起探討。我是這麼幹的,先準備工具
- jvms8官方檔案下載 - (因為我是用jdk8編譯的,所以下載的是8版本的。可以選擇自己的版本 https://docs.oracle.com/javase/specs/index.html
- idea外掛jclasslib Bytecode viewer - (代替javap命令直接在idea中檢視位元組碼編譯內容)
- sublime - 檢視16進位制位元組碼,方便閱讀 (也可以下載idea外掛BinEd)
菜瓜:寫一段最簡單的demo
- java原始檔
-
package club.interview.jvm; /**
* @author QuCheng on 2020/7/10.
*/
public class ClassOriginal {
}
-
- 二進位制 - 編譯成class檔案
-
11001010 11111110 10111010 10111110 00000000 00000000 00000000 00110100 00000000 00010000 00001010 00000000 00000011 00000000 00001101 00000111 00000000 00001110 00000111 00000000 00001111 00000001 00000000 00000110 00111100 01101001 01101110 01101001 01110100 00111110 00000001 00000000 00000011 00101000 00101001 01010110 00000001 00000000 00000100 01000011 01101111 01100100 01100101 00000001 00000000 00001111 01001100 01101001 01101110 01100101 01001110 01110101 01101101 01100010 01100101 01110010 01010100 01100001 01100010 01101100 01100101 00000001 00000000 00010010 01001100 01101111 01100011 01100001 01101100 01010110 01100001 01110010 01101001 01100001 01100010 01101100 01100101 01010100 01100001 01100010 01101100 01100101 00000001 00000000 00000100 01110100 01101000 01101001 01110011 00000001 00000000 00100010 01001100 01100011 01101100 01110101 01100010 00101111 01101001 01101110 01110100 01100101 01110010 01110110 01101001 01100101 01110111 00101111 01101010 01110110 01101101 00101111 01000011 01101100 01100001 01110011 01110011 01001111 01110010 01101001 01100111 01101001 01101110 01100001 01101100 00111011 00000001 00000000 00001010 01010011 01101111 01110101 01110010 01100011 01100101 01000110 01101001 01101100 01100101 00000001 00000000 00010010 01000011 01101100 01100001 01110011 01110011 01001111 01110010 01101001 01100111 01101001 01101110 01100001 01101100 00101110 01101010 01100001 01110110 01100001 00001100 00000000 00000100 00000000 00000101 00000001 00000000 00100000 01100011 01101100 01110101 01100010 00101111 01101001 01101110 01110100 01100101 01110010 01110110 01101001 01100101 01110111 00101111 01101010 01110110 01101101 00101111 01000011 01101100 01100001 01110011 01110011 01001111 01110010 01101001 01100111 01101001 01101110 01100001 01101100 00000001 00000000 00010000 01101010 01100001 01110110 01100001 00101111 01101100 01100001 01101110 01100111 00101111 01001111 01100010 01101010 01100101 01100011 01110100 00000000 00100001 00000000 00000010 00000000 00000011 00000000 00000000 00000000 00000000 00000000 00000001 00000000 00000001 00000000 00000100 00000000 00000101 00000000 00000001 00000000 00000110 00000000 00000000 00000000 00101111 00000000 00000001 00000000 00000001 00000000 00000000 00000000 00000101 00101010 10110111 00000000 00000001 10110001 00000000 00000000 00000000 00000010 00000000 00000111 00000000 00000000 00000000 00000110 00000000 00000001 00000000 00000000 00000000 00000110 00000000 00001000 00000000 00000000 00000000 00001100 00000000 00000001 00000000 00000000 00000000 00000101 00000000 00001001 00000000 00001010 00000000 00000000 00000000 00000001 00000000 00001011 00000000 00000000 00000000 00000010 00000000 00001100
-
- 16進位制
-
cafe babe 0000 0034 0010 0a00 0300 0d07
000e 0700 0f01 0006 3c69 6e69 743e 0100
0328 2956 0100 0443 6f64 6501 000f 4c69
6e65 4e75 6d62 6572 5461 626c 6501 0012
4c6f 6361 6c56 6172 6961 626c 6554 6162
6c65 0100 0474 6869 7301 0022 4c63 6c75
622f 696e 7465 7276 6965 772f 6a76 6d2f
436c 6173 734f 7269 6769 6e61 6c3b 0100
0a53 6f75 7263 6546 696c 6501 0012 436c
0c00 0400 0501 0020 636c 7562 2f69 6e74
6572 7669 6577 2f6a 766d 2f43 6c61 7373
4f72 6967 696e 616c 0100 106a 6176 612f
6c61 6e67 2f4f 626a 6563 7400 2100 0200
0300 0000 0000 0100 0100 0400 0500 0100
0600 0000 2f00 0100 0100 0000 052a b700
01b1 0000 0002 0007 0000 0006 0001 0000
0006 0008 0000 000c 0001 0000 0005 0009
000a 0000 0001 000b 0000 0002 000c
-
- 引用jvms8中的說明 - 虛擬機器器按照這個結構體對二進位制檔案進行解析
-
A class file consists of a single ClassFile structure: //單個class檔案結構組成
ClassFile {
u4 magic; // 前4位元組magic
u2 minor_version; // jdk小版本
u2 major_version; // 主要版本
u2 constant_pool_count; // 常量池大小
cp_info constant_pool[constant_pool_count-1]; // 常量池資訊
u2 access_flags; // 類訪問修飾符
u2 this_class; // 當前class指向常量池
u2 super_class; // 父類class指向常量池
u2 interfaces_count; // 介面總數
u2 interfaces[interfaces_count]; // 介面索引 - 若無介面,則無需統計
u2 fields_count; // 欄位統計
field_info fields[fields_count]; // 欄位資訊 - 若無成員欄位,則無需統計
u2 methods_count; // 方法統計
method_info methods[methods_count]; // 方法資訊 - 預設會有無參構造
u2 attributes_count; // 屬性
attribute_info attributes[attributes_count]; // 表示檔案資訊,譬如路徑和檔名
} - u4、u2 -- u表示無符號位,4和2表示位元組數
-
水稻:cafe babe 眼熟 ,0034轉換為10進位制是52,代表1.8版本。後面的倒是沒見過
菜瓜:那我們看一拿著前面一段來對照看看
-
cafe babe 0000 0034 0010 0a00 0300 0d07 按照結構體來劃分
u4 4個位元組 <cafe babe> 標頭檔案校驗
u2 2個位元組 <> minor_version小版本=0
u2 2個位元組 <> major_version大版本 52(16進位制) = 1.8
u2 2個位元組 <> 常量池大小 16(16進位制)
水稻:懂,後面的0a00 0300 0d07 是什麼呢? 按照結構體的排序是cp_info 常量池陣列根據下標編排的內容對吧
菜瓜:沒錯,這裡的cp_info 結構體有一個參照表如下
-
Constant_Type Value
CONSTANT_Class 7
CONSTANT_Fieldref 9
CONSTANT_Methodref 10
CONSTANT_InterfaceMethodref 11
CONSTANT_String 8
CONSTANT_Integer 3
CONSTANT_Float 4
CONSTANT_Long 5
CONSTANT_Double 6
CONSTANT_NameAndType 12
CONSTANT_Utf8 1
CONSTANT_MethodHandle 15
CONSTANT_MethodType 16
CONSTANT_InvokeDynamic 18 info 也是一個物件,不同物件屬性還不一樣
// tag=10 方法引用
CONSTANT_Methodref_info {
u1 tag;
u2 class_index; // class下標
u2 name_and_type_index; //
}
// tag = 7
CONSTANT_Class_info {
u1 tag;
u2 name_index; // class索引
} // tag = 1
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
} CONSTANT_NameAndType_info {
u1 tag;
u2 name_index;
u2 descriptor_index;
} - 再來看上面沒解析的 0a00 0300 0d07以及後續
-
0a00 0300 0d07 u1 1位元組 0a 表示10 對應10號結構體
CONSTANT_Methodref_info {
u1 tag;
u2 class_index; // class下標
u2 name_and_type_index; // 結構體下標
} u1 0a tag 結構體cp_info標識
u2 00 03 class_index 類檔案下標,此處指向常量池03位
u2 00 0d name_type_index 指向常量池13位
-
- 再列舉幾個,你應該就能看明白了
-
銜接第一排0d07
000e 0700 0f01 0006 3c69 6e69 743e 0100
0d已經被使用,從07開始
對照cp_info 07號結構體
// tag = 7
CONSTANT_Class_info {
u1 tag;
u2 name_index; // class索引
}
<07 000e>
u1 07
u2 000e 指向常量池第14位
<0700 0f>
u1 07
u2 000f 常量池第15位 下一位01 對應結構體
// tag = 1
CONSTANT_Utf8_info {
u1 tag;
u2 length;
u1 bytes[length];
}
<01 0006 3c69 6e69 743e>
u1 tag
u2 0006 後續位元組長度
u1 長度為6的位元組陣列 (3c69 6e69 743e) 對照ASCII表翻譯成字元("<init>")
-
水稻:妙啊!你怎麼證明是這樣的
菜瓜:不慌,用到第二個idea外掛工具jclasslib ... 怎麼使用我就不演示了。(也可以使用javap命令)編譯結果如下
- 類整體結構資訊
常量池資訊
後面的我就不貼了
水稻:原來是這樣解析的!!
菜瓜:當然這個其實沒什麼技術含量,只是一個比較死板的解析過程而已,不過這個設計真讓人拍案叫絕,只要最後java檔案能被編譯成這種class檔案格式,jvm就都能解析。後面有個區域需要熟悉一下:methods
水稻:哦?有什麼講究
菜瓜:我們方法的執行邏輯都在這裡,有個i++和++i的常見面試題可以從這裡一探究竟。因為我寫的這個demo比較簡單,此處的mthods區域只有一個方法就是預設的構造方法
- 要想看懂這個,還得拿jvms8的指令集對照表檢視- 第6章6.5
- aload_0 將this從區域性變量表載入到運算元棧棧頂 (aload_0指令 - 16進位制碼是2a)
- invokespecial 呼叫方法 - 這裡是呼叫的object的構造方法 (invokespecial指令 - 16進位制碼是b7)
- return - 返回結構 (return - 16進位制是 b1)
- 後面標註了指令對應的16進位制碼,可以呼應上面我們的16進位制對照表
- 2a b7 b1
水稻:有收穫,雖然對寫程式碼沒啥太大用,但是這個流程搞清楚了就比較通透
菜瓜:後面我還想繼續深入一下,有收穫再分享啊
水稻:可以可以
總結:
- 瞭解.java檔案被javac指令編譯後的位元組碼是如何解析的
- 文中demo過於簡單,此處只是為了演示方便。上面有提到i++和++i的面試題,大夥可以自己編譯去看看(https://www.cnblogs.com/nightOfStreet/p/13253792.html)