深入理解Java虚拟机04--类结构文件
一.程序存儲格式
- 統一的程序存儲格式:不同平臺的虛擬機于所有平臺都統一使用程序存儲格式——字節碼(ByteCode);
- Java 虛擬機不關心 Class 文件的來源,而只和“Class文件"這種二進制文件格式關聯,也就是說Java虛擬機只認識“Class"文件;
- Java 編譯器可以把 Java 程序代碼編譯成虛擬機所需要的Class 文件;
二.Class 文件結構
Class 文件是以 8 個字節為單位的二進制流,緊湊排列,中間沒有空隙;如果想查看一個 Class 文件除了通過 winHex 編譯器看到字節碼,也可以通過 javap -verbose xxx.Class 輸出字節碼內容,這樣看起來比較直觀。
1、基本類型
無符號數:
- 定義:基本的數據類型,u1、u8表示1個字節,8個字節。
- 使用:可以用來描述數字、索引、引用,utf-8格式的字符串;
表:
- 定義:多個無符號數和其他表組成的復合數據類型;通常以“_info” 結尾。
- 使用:描述有層次關系的復合結構數據;
2、魔數與版本
- 魔數:每個Class文件的頭4個字節,唯一作用就是確定這個文件是否能被一個虛擬機接受的Class文件;
- 次版本號:緊接著魔數后面的第5和第6個字節;
- 主版本號:第7和第8個字節代表主版本號,比如說50對應的就是JDK1.6.
- 可以使用十六進制編譯器WinHex打開一個Class文件瞅瞅;
3、常量池
版本號之后緊跟的就是常量池入口,可以理解為Class文件之中的資源倉庫;
- 常量池容量計數器:u2類型,代表本Class文件有N-1個常量(因為是從1開始技術的);
- 常量池放置的內容每一項都是一個表,主要分兩類
- 類和接口的全限定名
- 字段的名稱和描述符
- 方法的名稱和描述符
- length:
4、訪問標志(access_flags)
- 常量池之后緊跟的就是訪問標志。主要包括了這個Class是類or接口,是不是 public,是不是 abstract 類型,是不是 final 類型。
5、類索引、父類索引和接口類集合
- java.lang.Object類索引為0;
- 類的索引其實就是描述了這個Class的extends和implements的關系;
6、字段表集合(field_info)
- 用于描述定義的變量,依次包括了訪問標志(access_ flags)、名稱索引(name_ index)、描述索引(descriptor_ index)、屬性表集合(attributes)。
- 描述的信息如下:
- 字段表集合原則
7、方法表集合
和字段表集合差不多,方法表集合用來描述Class文件中的方法,但是訪問標志和屬性表集合和字段表集合有所區別;
- 訪問標志:
- volatile、transient關鍵字不可以修飾方法,方法表中少了這兩種標志;
- synchronized、native、strictfp和abstract可以修復方法,故方法表增加了這些對應的標志;
- Code屬性:
- 方法體中的代碼放在了“Code”屬性里面了;
- 方法表集合原則
- 方法沒有重寫(Override),父類的信息不會寫到子類的方法表中;
- 編譯器有可能自動添加一些方法,典型的如類構造器的“< clinit >”、方法&實例構造器的“< init >“方法;
- 重載(Overload)一個方法,需要添加一個特征簽名,特征簽名就是一個方法中各個參數在常量池中的字段符號引用的集合;
8、屬性表集合(attribute_info)
上述那些表需要攜帶自己的某些屬性,來描述自己的特殊環境信息,比如InnderClasses、LineNumberTable、Code 之類的;
- Code (用語描述代碼)
- max_stack:操作棧深度最大值,JVM 運行時根據這個值來分配棧幀(Stack Frame)中的操作棧深度;
- max_locals:代表了局部變量表所需要的存儲空間。
- Slot:虛擬機為局部變量分配內存的最小單位
- byte、char、float、int、short、boolean、returnAddress 長度少于32位,占1個slot
- double、long 64位,占2個slot
- 當代碼超出一個局部變量的作用域時,這個局部變量所占用的 slot 可以被其他的局部變量所使用
- Slot:虛擬機為局部變量分配內存的最小單位
- code_length:字節碼長度
- code:存儲字節碼指令
- 65535限制:虛擬機規定了一個方法不允許超過 65535 條字節碼,否則編譯不通過;
- 執行:執行過程中的數據交換、方法調用等操作都是基于棧(操作棧)的;
- this關鍵字:在實例方法中通常可以有個 this 關鍵字來引用當前對象的變量,這是因為 Java 編譯時在局部變量表中自動增加了這個(this)局部變量。
- LineNumberTable:描述 Java 的源碼行號和字節碼行號;
- LocalVariableTable:描述局部變量表中的變量與Java源碼中定義的變量之間的關系;
?
三.字節碼指令
1、字節碼組成
- 操作碼(Opcode):i(助記符)代表int類型數據操作....等等;
- 操作數 (Operands):永遠都是一個數組類型的對象;
Java虛擬機采用面向操作數棧而不是寄存器的架構,字節碼指令集是一種指令集架構。放棄了操作數對齊,省略了填充的符號和間隔。
2、加載和存儲指令
將數據在幀棧中將局部變量表和操作數棧之間來回傳輸。
- 將一個局部變量加載到操作棧;
- 將一個數值從操作數棧存儲到局部變量表;
- 將一個常量加載到操作數棧;
- 擴充局部變量表的訪問索引的指令;
3、運算指令
- 將兩個操作數棧上的值進行某種特定運算,并把結果重新存入到操作棧頂;
- Java沒有直接支持byte、short、char、boolean類型,都轉為int類型進行運算,使用int的指令代替;
4、類型轉換指令
- 寬化轉換
- int到long、float、double
- long到float、double
- float到double
- 窄化轉換
- 必須顯示的聲明轉換
- 有溢出或者丟精的情況,但不會拋出異常
5、同步指令
- Java虛擬機支持方法級同步和方法內部一段指令序列同步,這兩種同步都是通過“管程”來支持;
- 執行線程就要求先成功持有“管程”,然后才能執行方法,最后方法執行完成后,才釋放“管程”。
- Java虛擬機通過 monitorenter 和 monitorexit兩個指令配對使用,另外編譯器會自動增加一個異常處理器。當出現異常時,這個異常處理器能夠捕獲到所有的異常,并且釋放“管程”,monitorexit 指令響應。這樣的話,保證了 monitorenter 和monitorexit 總是成對出現的。
?三.代碼舉例
1、Java文件:
1 package com.xxx.ccc; 2 public final class InitConfig { 3 public static final InitConfig BFCACCOUNT = new InitConfig(0, "aaa", "AAA"); 4 private int mIndex; 5 private String mData; 6 private String mDescribe; 7 private InitConfig(int indexFlag, String data, String describe) { 8 this.mIndex = indexFlag; 9 this.mData = data; 10 this.mDescribe = describe; 11 } 12 public String getmData() { 13 return this.mData; 14 }2、Class 文件:
1 Last modified 2017-7-4; size 1050 bytes 2 MD5 checksum 2beb0c10f91b793c3570edcf2d1eff78 3 Compiled from "InitConfig.java" 4 public final class com.xxx.xxx.InitConfig 5 minor version: 0 //次版本號 6 major version: 51 //主版本號 7 flags: ACC_PUBLIC, ACC_FINAL, ACC_SUPER //訪問標志 8 Constant pool: //常量池 9 #1 = Methodref #14.#41 // java/lang/Object."<init>":()V 10 #2 = Fieldref #5.#42 // com/xxx/xxx/InitConfig.mIndex:I 11 #6 = Class #46 // com/xxx/xxx/common/constant/ConstData 12 #7 = String #47 // aaa 13 #23 = Utf8 <init> 14 #24 = Utf8 (ILjava/lang/String;Ljava/lang/String;)V 15 #25 = Utf8 Code 16 #26 = Utf8 LineNumberTable //Java的源碼行號和字節碼行號 17 #27 = Utf8 LocalVariableTable //局部變量表中的變量與Java源碼中定義的變量之間的關系 18 #28 = Utf8 this 19 #32 = Utf8 getmData 20 #33 = Utf8 ()Ljava/lang/String; 21 #37 = Utf8 <clinit> 22 #38 = Utf8 ()V 23 #40 = Utf8 InitConfig.java 24 #41 = NameAndType #23:#38 // "<init>":()V 25 #45 = Utf8 com/xxx/xxx/InitConfig 26 #46 = Utf8 com/xxx/xxx/common/constant/ConstData 27 #53 = NameAndType #17:#16 // SEAACCOUNT:Lcom/xxx/ccc/InitConfig; 28 #54 = Utf8 java/lang/Object 29 public static final com.xxx.xxx BFCACCOUNT; 30 descriptor: Lcom/xxx/xxx/InitConfig; 31 flags: ACC_PUBLIC, ACC_STATIC, ACC_FINAL 32 public java.lang.String getmData(); 33 descriptor: ()Ljava/lang/String; 34 flags: ACC_PUBLIC 35 Code: 36 stack=1, locals=1, args_size=1 37 0: aload_0 38 1: getfield #3 // Field mData:Ljava/lang/String; 39 4: areturn 40 LineNumberTable: //Java的源碼行號和字節碼行號 41 line 36: 0 42 LocalVariableTable: //局部變量表中的變量與Java源碼中定義的變量之間的關系 43 Start Length Slot Name Signature 44 0 5 0 this Lcom/xxx/xxx/InitConfig; //方法里面默認增加了個this四.小結
為什么說一些”非Java"語言也是可以在 JVM 上跑,這是因為 JVM 只認識 Class 文件,所以如果某某語言最終編譯出的文件是 Class 文件,那么對于 JVM 來說沒有什么區別,但是得按照 Class 文件的結構來,不然也無法正常執行。Class 定義了許多特定的基本類型和表結構,通過魔數讓 JVM 認識該文件,版本號保證可以在要求的 JDK 版本上運行,在常量池中定義好常量,訪問標志位確定訪問權限。索引集合方便與外界的 class 保持聯系,字段表保存我們定義好的變量,方法表存儲方法的信息,屬性表存儲了上述各種表的一些屬性。其中記住 slot為局部變量分配內存的最小單位,當程序超出作用域的時候,slot 可以被其他替換使用。到這里,僅僅是代碼最靜態的存儲的格式,程序要運行起來。還需要操作指令,也是由字節碼存儲,包括操作碼和操作數。有加載存儲、運算、類型轉換、同步指令。
總結
以上是生活随笔為你收集整理的深入理解Java虚拟机04--类结构文件的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 移动前端开发经验小结
- 下一篇: pandas数据结构:Series/Da