【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文件
- 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 6173 734f 7269 6769 6e61 6c2e 6a61 7661 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個字節 <0000> minor_version小版本=0 u2 2個字節 <0034> major_version大版本 52(16進制) = 1.8 u2 2個字節 <0010> 常量池大小 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 )