水稻:看你研究盯着這個文檔一天了,什麼玩意讓人心馳神往

菜瓜:前幾天意外得到一本武功祕籍《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

水稻:有收穫,雖然對寫代碼沒啥太大用,但是這個流程搞清楚了就比較通透

菜瓜:後面我還想繼續深入一下,有收穫再分享啊

水稻:可以可以

總結:

相關文章