《深刻理解Java虛擬機》筆記三

大部份內容都是《深刻理解Java虛擬機上的內容》的總結,少部份內容是來自於網上或者本身的理解。讀完應該會把沒筆記的markdown文件放在 github上。

本部分筆記對應的是《深刻理解Java虛擬機》第七章,第八章和第九章。java

虛擬機類加載機制

加載(Loading),驗證(Verification),準備(Preparation),解析(Resolution),初始化(Initialization),使用(Using)和卸載(Unloading)7個階段,其中驗證,準備,解析3個階段稱爲鏈接(Linking)。git

加載,驗證,準備,初始化和卸載這5個階段的順序是必定的。github

加載的時機

  1. 遇到new,getstatic,putstatic,或invokestatic這4個字節碼指令時,若是類沒有進行過初始化,則須要先觸發其初始化
  2. 使用反射類調用時,若是類沒有進行初始化,則須要先觸發其初始化
  3. 當初始化一個類的時候,發現其父類尚未初始化,則須要先觸發父類
  4. 當虛擬機啓動時,用戶須要指定一個執行類的主類,虛擬機會先初始化這個主類
  5. 若是一個java.lang.invoke.MehtodHandle實例最後的解析結果REF_getStatic, REF_putStatic, REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行初始化,則須要先觸發其初始化

類加載的過程

加載

  1. 經過一個類的全限定名來獲取定義此類的二進制字節流
  2. 將這個字節流所表明的靜態數據結構轉化爲方法區的運行時數據結構
  3. 在內存中生成一個表明這個類的java.lang.Class對象,做爲方法區這個類的各類數據的訪問接口

驗證(很是重要,但沒必要須)

  1. 文件格式的驗證安全

    主要目的是保證輸入的字節流能正確地解析並存儲於方法區以內,格式上符合描述一個Java類型信息的要求。經過這個驗證以後,字節流纔會進入內存的方法區中存儲,因此後面的3個驗證階段都是基於方法區的存儲結構進行的。markdown

    1. 元數據驗證

      對字節碼描述的信息進行語義分析,保證不存在不符合Java語言規範的元數據信息。數據結構

    2. 字節碼驗證

      經過數據流和控制流分析,肯定程序語義是合法的,符合邏輯的。在第二階段對元數據信息中的數據類型作完校驗後,這個階段對類的方法體進行校驗分析。架構

    3. 符號引用驗證

      確保解析動做可以正常的進行。模塊化

#### 準備

是正式爲**類變量**分配內存並設置**類變量**初始值的階段,這些變量所使用的內存都將在方法區中進行分配。這裏的初始值「一般狀況下」是數據類型的零值。

public static int value = 123

這裏value的值是0。

若是類字段的字段屬性表中存在ConstantValue,則會在準備階段就進行賦值

public static final int value = 123

這時value的值是123。

#### 解析

虛擬機將常量池內的符號引用替換爲直接引用的過程。

**符號引用(Symbolic Reference)**

符號引用以一組符號來描述所引用的目標,符號能夠是任何形式的字面量,只要使用時能無歧義地定位當前目標便可。**符號引用與虛擬機實現的內存佈局無關,引用的目標並不必定已經加載到內存中。**各個虛擬機實現的內存佈局能夠不一樣,可是它們能接受的符號引用必須都是一直的,由於符號引用的字面量形式明肯定義在Java虛擬機規範的Class文件中。

**直接引用(Direct Reference)**

直接引用能夠是直接指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的內存佈局有關。若是有了直接引用,那引用的目標並定已經在內存中實現了。

#### 初始化

**類初始化是類加載過程當中的最後一步,前面的類加載過程當中,除了在加載階段用戶應用程序能夠經過自定義類加載器參與以外,其他動做徹底是由虛擬機主導和控制的。**

初始化階段時執行類構造器<clinit>()方法的過程。

<clinit>()是有編譯器自動收集類中的全部類變量的賦值動做和靜態語句塊中的語句合併產生的。

### 類加載器

對於任意一個類,都須要由加載它的類加載器和這個類自己一同肯定其在Java虛擬機中的惟一性

#### 雙親委派模型

從Java虛擬機的角度來說

*   啓動類加載器(Bootstrap ClassLoader)這個類加載器使用C++語言實現,是虛擬機的一部分。
    
*   全部其餘的類加載器,這些類加載器都由Java語言實現,獨立於虛擬機外部,並都繼承抽象類java.lang.ClassLoader。
    

從Java開發人員角度分爲是分爲3種

*   啓動類加載器,負責加載<JAVA\_HOME>\\lib目錄,或被-Xbootclasspath指定的路徑,並被虛擬機識別的類庫加載到虛擬機內存中。
    
*   擴展類加載器(Extension ClassLoader),負責加載<JAVA\_HOME>\\lib\\ext目錄中,或者被java.ext.dirs系統變量所指定的路徑中的全部類庫。
    
*   應用程序類加載器(Application ClassLoader),又叫系統類加載器,通常狀況下這個就是程序中默認的類加載器。
    

**工做流程**

若是一個類加載器收到了類加載的請求,它首先不會本身去嘗試加載這個類,而是把這個請求委派給父類加載器去完成,每一層次的類加載器都是如此,所以全部的加載請求最終都應該傳送到頂層的啓動類加載器中,只有當父加載器反饋本身沒法完成這個加載請求時,子加載器纔會嘗試本身加載。

虛擬機字節碼執行引擎
----------

### 運行時棧幀結構

棧幀(Stack Frame)是用於支持虛擬機進行方法調用和方法執行的數據結構,它是虛擬機運行時數據區中的虛擬機棧(Virtual Machine Stack)的棧元素。棧幀存儲了方法的局部變量表,操做數棧,動態鏈接和方法返回地址等信息。每個方法從調用開始至執行完成的過程,都對應着一個棧幀在虛擬機棧裏面從入棧道出棧的過程。

只有位於棧頂的棧幀纔是有效,稱爲當前棧幀(Current Stack Frame),與這個棧幀相關聯的方法叫作當前方法(Current Mehod)。

#### 局部變量表

局部變量表(Local Varible Table)是一組變量值存儲空間,用於存放方法參數和方法內定義的局部變量。在Java程序編譯爲Class文件時,就在方法的Code屬性中肯定了最大容量。

局部變量表的容量以容量槽(Varible Slot)爲最小單位。

**因爲局部變量表創建在線程的堆棧上,是線程私有的數據,不管讀寫兩個鏈接的Slot是否爲原子操做,都不會一塊兒數據安全問題。**

在方法執行時,虛擬機是使用局部變量表完成參數值到參數變量列表的傳遞過程。先是分配參數,以後再根據方法體內部定義的變量順序和做用域分配其他的Slot

#### 操做數棧

操做數棧(Operand Stack),當方法剛剛開始執行的時候,這個方法的操做數棧是空的,在方法的執行過程當中,會有各類字節碼指令往操做數棧中寫入和提取數據。

#### 動態鏈接

每一個棧幀都包含一個指向運行時常量池中該棧幀所屬的方法的引用,持有這個引用是爲了支持方法調用過程當中的動態鏈接。

#### 方法返回地址

第一種是執行引擎遇到了任意一個方法返回的字節碼指令。這種退出方法稱爲正常完成出口(Normal Method Invocation Completion)

另外一種,在方法執行遇到了異常,而且這個異常沒有在方法體內獲得處理。不管是Java虛擬機內部產生的異常,仍是代碼中使用athrow字節碼指令產生的異常。只要在本方法的異常表中沒有搜索到匹配的異常處理器,就會致使方法退出,這種退出方式稱爲異常完成出口(Abrupt Method Invocation Completion)

### 方法調用

方法調用並不等同於方法執行,方法調用階段惟一的任務就是肯定被調用方法的版本,暫時還不涉及方法內部的具體運行過程。

#### 解析

字節碼中的方法調用指令就一常量池中指向方法的符號引用做爲參數。這些符號引用一部分會在類加載階段或者第一次使用的時候就轉換爲直接引用,這種轉化稱爲**靜態解析**。另一部分將在每一次運行期間轉化爲直接引用,這部分稱爲**動態鏈接**。

在類加載階段,會將其中的一部分符號引用,轉化爲直接引用,這種解析能成立的前提是:方法在程序真正運行以前就有一個可肯定的目標,而且這個方法調用版本在運行期不可修改。主要包括靜態方法和私有方法兩大類。

**非虛方法**

只要能被invokestatic和invokespecial指令調用的方法,均可以在解析階段中肯定惟一的調用版本,符合這個條件的有靜態方法,私有方法,實例構造器,父類方法,它們在類加載的時候就會把符號引用解析爲該方法的直接引用。這些方法稱爲非虛方法。

final方法也是一種非虛方法。

### 分派

#### 靜態分派

全部依賴靜態類型來定位執行版本的分派動做,稱爲靜態分派。靜態分派的典型應用是**方法重載**。靜態分派發生在**編譯階段**,**所以肯定靜態分派的動做實際上不是由虛擬機執行。**有時自變量不須要定義,因此字面量沒有顯示的靜態類型,它的靜態類型只能經過語言上的規則區理解和推斷。

#### 動態分派

動態分派和**重寫**有着密切關係。

**實現**

爲類在方法區中創建一個虛方法表(Virtual Mehtod Table,在invokeinterface執行時也會用到接口方法表Inteface Method Table)。虛方法表中存放着各個方法的實際入口地址。

**方法表通常在類加載的鏈接階段進行初始化,準備了類的變量初始值後,虛擬機會把該類的方法表也初始化完成。**

#### 單分派和多分派

方法的接收者與方法的參數統稱爲方法的宗量。根據分派基於多少種宗量,能夠分派劃分爲單分派和多分派。

單分派是根據一個宗量對目標方法進行選擇,多分派是根據多於一個宗量對目標方法進行選擇。

#### 動態類型支持

java.lang.invoke

### 基於棧的字節碼解釋執行引擎

Java編譯器輸出的指令流,基本上是一種基於棧架構的指令集架構(Instruction Set Architecture IA),指令流中的指令大部分都是基於零地址指令,他們依賴於操做數棧進行工做的。

基於棧的指令集主要優勢就是可移植,寄存器由硬件直接提供,程序直接依賴這些硬件寄存器則不可避免的受到硬件約束。主要缺點,執行速度相對會稍慢一點。

類加載及執行子系統的案例與實戰
---------------

### Tomcat

在Tomcat目錄結構中,有3組目錄(「/common/\*」,「/server/\*」和「/shared/\*」)能夠存放Java類庫,另外還能夠加上Web應用程序自身的目錄「WEB-INF」

放置在/common目錄中:類庫能夠被Tomcat和全部的Web應用程序共同使用

放置上/server目錄中:類庫能夠被Tomcat使用,對全部的Web應用程序均可不見

放置在/shared目錄中:類庫可被全部的Web應用程序共同使用,但對Tomcat本身不可見

放置在/WebApp/WEB-INF目錄中:類庫僅僅被此Web應用程序使用,對Tomcat和其餘Web應用程序都不可見

### OSGI

一種動態模塊化規範
相關文章
相關標籤/搜索