深入理解Java虛擬機器筆記_第6章

2020-08-12 10:48:45

Class類檔案的結構

  1. 以八個位元組爲基礎單位的二進制流
  2. 無符號數爲基本數據型別。以u1,u2,u4,u8分別代表1位元組,2位元組,4位元組,8位元組的無符號數
  3. 表是由多個無符號數或者其他表作爲數據項構成的複合數據型別。習慣以_info結尾

魔數與Class檔案的版本

  1. 頭四個位元組爲魔數,標識他爲Class檔案。值爲0xCAFEBABE
  2. 後面四個位元組爲版本號。前兩個爲次版本號(Minor Version),後兩個爲主版本號(Minor Version)。次版本號基本未使用,固定爲0。主版本號從45開始。
  3. 高版本JDK能向下相容以前的Class檔案但是不能執行之後版本的Class檔案
  4. 例如JDK1.1能支援的版本號爲:45.0-45.65535,JDK1.2支援45.0-46.65535

常數池

  1. 主要存放字面量和符號參照。字面量類似常數,如文字字串,被宣告爲final的常數值等等。符號參照屬於編譯原理相關,包括全限定類名,方法欄位名稱等等
  2. 符號參照不經過虛擬機器執行期轉換就無法得到真正的記憶體入口地址,就無法被虛擬機器直接使用。當虛擬機器做類載入時,將從常數池獲取相應的符號參照,再在類建立時或執行時解析翻譯到具體的記憶體地址中。

CONSTANT_Class_info

  1. 用u1儲存tag,標識常數型別
  2. 用u2儲存name_index,是常數池的索引值,指向一個CONSTANT_Utf8_info型別常數,代表了該類的全限定名。

CONSTANT_Utf8_info

  1. 用u1儲存tag,標識常數型別
  2. 用u2儲存length,代表字串長度是多少位元組
  3. 之後是用u1儲存bytes,一共有length個位元組

存取標誌

常數池之後兩個位元組代表存取標誌。例如:是類還是介面,是否是public,是否是abstract等等

類索引,父類別索引,介面索引集合

  1. 類索引,父類別索引爲u2型別的數據,介面索引集合爲u2型別的數據集合。由此確定該型別的繼承關係。
  2. 類索引,父類別索參照u2型別的索引值表示,各自指向一個CONSTANT_Class_info的類描述符常數。又通過該常數的CONSTANT_Utf8_info找到全限定名字串。
  3. 介面索引集合第一項的u2型別的數據爲介面計數器,表示索引表的容量。

欄位表集合

  1. 描述變數,包括類級變數,範例級變數,但不包括方法中的區域性變數。
  2. 欄位包括: 修飾符(public,private,protected,static,final,volatile,transient),字元數據型別(基本型別,物件,陣列),欄位名稱
  3. 修飾符適合用標誌位表示,但字元數據型別,欄位名稱只能參照常數池中的常數來描述
  4. 字元修飾符用u2,access_flags表示。之後是u2的name_index,descriptor_index是對常數池的參照,代表了欄位的簡單名稱和方法的描述符。
  5. 描述符用來描述欄位的數據型別,方法的參數列表和返回值。基本數據型別型別和void用一個大寫字元表示。例如C代表char。物件型別用L加物件的全限定名錶示,例如Ljava/lang/Object;
  6. 陣列每一維度用一個[表示,如定義了java.lang.String [][]則記錄爲[[Ljava/lang/String;
  7. 用描述符描述方法,則先參照列表,再返回值的順序描述。例如String test(char [] chars)([C)[Ljava/lang/String;
  8. 欄位表集合不會列出從父類別或者父介面中繼承來的欄位。但有可能出現Java程式碼中不存在的欄位。例如編譯器爲內部類自動新增指向外部類範例的欄位。

方法表集合

  1. 和欄位表集合極其相似。沒有volatile,transient,但多了synchronized,native,strictfp,abstract關鍵字
  2. 方法中的程式碼,存放再方法屬性表集閤中名爲Code的屬性裏面
  3. 若子類沒有重寫父類別方法,則不會出現父類別方法資訊
  4. 有可能出現由編譯器自動新增的方法,例如類構造器<cinit>(),範例構造器<init>()方法

屬性表集合

Class檔案,欄位表,方法表都可以攜帶自己的屬性表集合,以描述某些場景專有的資訊。

Code屬性

  1. 方法體中程式碼經過Javac編譯器處理後,變成位元組碼指令儲存在Code屬性中
  2. attribute_name_index是指向CONSTANT_Utf8_info的索引,固定爲"Code",代表該屬性名稱
  3. attribute_length指示了屬性值的長度,爲u4
  4. max_stack代表了運算元棧深度的最大值。根據該值來分配棧幀中的運算元棧深度。
  5. max_locals代表了區域性變數表所需的儲存空間。單位是Slot(變數槽),變數槽是虛擬機器爲區域性變數分配記憶體所使用的最小單位。不超過32位元的一個槽,double和long佔用兩個槽。
  6. 方法參數(包括實體方法隱藏參數this),異常參數,方法體中的區域性變數都依賴區域性變數表儲存
  7. 虛擬機器將區域性變數表中的變數槽複用。當程式碼執行超出了一個區域性變數的作用域時,該區域性變數所佔的變數槽可以被其他區域性變數使用。根據同時生成的最大區域性變數數量和型別算出max_locals的大小
  8. code_length,code儲存Java源程式編譯後生成的位元組碼指令。每個指令是u1的單位元組。可以對應找出該位元組碼代表什麼指令。
  9. Code屬性用於描述程式碼,其他專案用於描述元數據。
  10. 任何實體方法裏面,都可以通過this存取到該方法所屬的物件。是通過編譯器編譯時把對this關鍵字的存取轉變位對一個普通方法參數的存取,虛擬機器在呼叫實體方法時自動傳入此參數。因此任何實體方法的區域性變數表至少存在一個指向當前物件範例的區域性變數。區域性變數表也會預留出第一個變數槽位來存放範例物件的參照。
  11. 異常表,當位元組碼從第start_pc到第end_pc出現了型別爲catch_type或其子類的異常,則轉到第handle_pc行繼續處理。

Exceptions屬性

列舉出方法中可能拋出的受查異常。即方法中throws後面列舉的異常。

LineNumberTable屬性

描述Java原始碼行號和位元組碼行號的對應關係。非必須,但如果沒有,拋出異常時不會顯示出錯行號,偵錯程式也無法按照原始碼設定斷點。

LocalVariableTable和LocalVariableTypeTable屬性

描述棧幀中區域性變數表的變數和Java原始碼中定義的變數之間的關係。非必須,但沒有當其他人參照方法,所有參數名稱將會丟失,用arg0,arg1等替代參數名。

SourceFile屬性

用於記錄生成這個Class檔案的原始碼檔名稱.大多數類,類名和檔名一致,但有例外。

ConstantValue屬性

  1. 通知虛擬機器自動爲靜態變數賦值。
  2. 對範例變數賦值再()方法中。
  3. 對於類變數,若是final,static修飾,並且是基本型別或者String的話,用ConstantValue進行初始化。否則會選擇在<cinit()方法初始化。

InnerClasses屬性

用於記錄內部類和宿主類之間的關聯。

Deprecated及Synthetic屬性

Deprecated屬性用於表示某個類,欄位或者方法,已經被程式作者定爲不再推薦使用。可以用@deprecated註解進行設定。

Synthetic屬性標識此欄位或方法不是由Java原始碼直接產生的,而是由編譯器自行新增的。

StackMapTable屬性

在虛擬機器類載入的位元組碼驗證階段被新型別檢查驗證器使用。

Signature屬性

若包含了型別變數或參數化型別,則該屬性會爲他記錄泛型簽名資訊

BootStrapMethods屬性

儲存invokedynamic指令參照的引導方法限定符

MethodParameters屬性

記錄方法的各個形參名稱和資訊

模組化相關屬性

支援Java模組化相關功能

執行時註解相關屬性

記錄了類,欄位,方法的宣告上記錄執行時可見註解

位元組碼指令簡介

由一個位元組長度的操作碼和零或多個的運算元構成.是面向運算元棧的,所以大多數指令不包括運算元,只有操作碼.指令參數都放在運算元棧中。

位元組碼與數據型別

  1. iload指令用於從區域性變數表中載入int型的數據到運算元棧中
  2. 大多數指令沒有支援byte,char,short,boolean。編譯器會在編譯或者執行期將他們擴充套件爲int型數據

載入和儲存指令

用於將數據在棧幀中的區域性變數表和運算元棧之間來回傳輸

運算指令

用於對運算元棧上的兩個值進行某種特定運算,並且把結果重新存入操作棧頂

大致分爲兩種型別:對整數數據,浮點型數據

不存在直接支援byte,short,char,boolean的運算,都是用操作int型別的指令代替

型別轉換指令

  1. 將兩種不同的數據型別相互轉換,實現使用者程式碼中的顯式型別轉換
  2. 小範圍到大範圍型別爲安全轉換,無須顯示的轉換指令,如int 轉 long,float,double,long轉folat,double,float轉double
  3. int轉long,只會簡單的將高位拋棄,所以可能導致不同的正負號
  4. 轉換可能出現上限溢位,下限溢位,精度丟失等等,但永遠不可能導致虛擬機器拋出執行時異常

物件建立與存取指令

Java虛擬機器對類範例和陣列的建立使用了不同的位元組碼指令

運算元棧管理指令

將運算元棧頂一個或者兩個元素出棧:pop,pop2

將棧頂最頂端兩個數值交換:swap

控制轉移指令

有條件或無條件的修改PC暫存器的值,從指定位置指令的下一條指令繼續執行程式

各種型別的比較最終都會轉化爲int型別的比較操作

方法呼叫和返回指令

  1. invokevirtual:用於呼叫物件的實體方法,根據物件的實際型別進行分派(虛方法分派)
  2. invokeinterface:用於呼叫介面方法,在執行時搜尋一個實現了這個介面方法的物件,找出合適的方法呼叫
  3. invokespecial:呼叫一些需要特殊處理的實體方法,例如範例初始化方法,私有方法,父類別方法
  4. invokestatic:用於呼叫靜態方法(static)方法
  5. invokedynamic:用於在執行時動態解析出調用點限定符所參照的方法
  6. 方法返回指令是根據返回值的型別區分的.包括ireturn(返回型別是boolean,byte,char,short,int),lreturn,freturn,dreturn,areturn,return(返回值爲void的,範例初始化的,類和介面的類初始化方法)

例外處理指令

對顯示拋出異常的操作(throw語句)進行處理

而處理異常(catch語句)不由位元組碼指令實現,是由異常表實現

同步指令

Java虛擬機器可以支援方法級同步和方法內部一段指令序列的同步,使用管程實現.

執行執行緒要求先成功持有管程,然後才能 纔能執行方法,最後當方法完成時釋放管程

使用synchronized語句塊表示

無論這個方法是正常結束還是異常退出,方法呼叫的每條monitorenter指令都必須有其對應的monitorexit指令

公有設計,私有實現

<<Java虛擬機器規範>>描繪了Java虛擬機器應有的共同程式儲存格

式:Class檔案格式以及位元組碼指令集

任何一款Java虛擬機器都必須能讀取Class檔案並且精確實現其中的Java虛擬機器程式碼的語意

Java虛擬機器只要外部介面與規範描述一致即可,具體實現不規定

小結

Class檔案格式具備平臺中立,緊湊,穩定,可延伸性的特點。是Java技術體系實現平臺無關,語言無關的重要支柱

<<Java虛擬機器規範>>描繪了Java虛擬機器應有的共同程式儲存格

式:Class檔案格式以及位元組碼指令集

任何一款Java虛擬機器都必須能讀取Class檔案並且精確實現其中的Java虛擬機器程式碼的語意

Java虛擬機器只要外部介面與規範描述一致即可,具體實現不規定

小結

Class檔案格式具備平臺中立,緊湊,穩定,可延伸性的特點。是Java技術體系實現平臺無關,語言無關的重要支柱

Class檔案是Java虛擬機器執行引擎的數據入口。