1. 程式人生 > 實用技巧 >Java虛擬機器類載入

Java虛擬機器類載入

https://baijiahao.baidu.com/s?id=1636309817155065432&wfr=spider&for=pc

1. 什麼是類的載入

  下圖展示類的載入機制在整個 java 程式執行期間處於什麼環節。

  

  java 檔案通過編譯器編譯成 .class 檔案,接下來類載入器又將這些 .class 檔案載入到 JVM 中。其中,類裝載器的作用就是類的載入。

  類的載入指的是將類的 .class 檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區內,然後在堆區建立一個 java.lang.Class 物件,用來封裝類在方法區的資料結構。

2. 在什麼時候才會啟動類載入器

  其實,類載入器並不需要等到某個類被“首次主動使用”時再載入它,JVM 規範允許類載入器在預料某個類將要被使用時就預先載入它,如果在預先載入的過程中遇到了 .class 檔案缺失或者存在錯誤,類載入器必須在程式首次主動使用該類時才報告錯誤(LinkageError 錯誤),如果這個類一直沒有被程式主動使用,那麼類載入器就不會報告錯誤。

3. 從哪些地方去載入 .class 檔案

  一般有5個來源:

  1)本地磁碟

  2)網上載入 .class 檔案(Applet)

  3)從資料庫中

  4)壓縮檔案中(ZAR,jar 等)

  5)其他檔案生成的(JSP 應用)

4. 類載入的過程

  類從被載入到虛擬機器記憶體中開始,到卸載出記憶體為止,它的整個生命週期包括:載入、驗證、準備、解析、初始化、使用、解除安裝 七個階段。

  

  其中,類的載入包括了:載入、驗證、準備、解析、初始化 五個階段。在這五個階段中,載入、驗證、準備和初始化這四個階段發生的順序是確定的,而解析階段則不一定,它在某些情況下可以在初始化階段之後開始。另外注意這裡的幾個階段按順序開始,而不是按順序進行或完成,因為這些階段通常都是互相交叉地混合進行的,通常在一個階段執行的過程中呼叫或啟用另一個階段。

4.1 載入

  載入是類載入機制的第一個過程,在載入階段,虛擬機器主要完成三件事:

  1)通過一個類的全限定名來獲取其定義的二進位制位元組流

  2)將這個位元組流所代表的靜態儲存結構轉化為方法區的執行時資料結構

  3)在堆中生成一個代表這個類的 Class 物件,作為方法區中這些資料的訪問入口

  相對於類載入的其他階段而言,載入階段是可控性最強的階段,因為程式設計師可以使用系統的類載入器載入,也可以使用自己的類載入器載入。

4.2 驗證

  驗證的主要作用就是確保被載入的類的正確性。也是連線階段的第一步。檢測驗證載入好的 .class 檔案不會對虛擬機器有危害。主要是完成四個階段的驗證:

  1)檔案格式的驗證:驗證 .class 檔案位元組流是否符合 class 檔案的格式的規範,並且能夠被當前版本的虛擬機器處理

  2)元資料驗證:主要是對位元組碼描述的資訊進行語義分析,以保證其描述的資訊符合 java 語言規範的要求

  3)位元組碼驗證:這是整個驗證過程最複雜的階段,主要通過資料流和控制流分析,確定程式語義是合法的、符合邏輯的。在元資料驗證階段對資料型別做出驗證後,這個階段主要對類的方法做出分析,保證類的方法在執行時不會做出危害虛擬機器安全的事

  4)符號引用驗證:它是驗證的最後一個階段,發生在虛擬機器將符號引用轉化為直接引用的時候。主要是對類自身以外的資訊進行校驗。目的是確保解析動作完成

  對整個類載入機制而言,驗證階段是一個很重要但是非必需的階段,如果我們的程式碼確保沒有問題,就不用驗證,使用 -Xverfity:none 來關閉大部分的驗證

4.3 準備

  準備階段主要為類變數分配記憶體並設定初始值。這些記憶體都在方法區分配。在這個階段重點注意 類變數和初始值。

  1)類變數會分配記憶體,但是例項變數不會,例項變數主要隨著物件的例項化一塊分配到 java 堆中

  2)這裡的初始值指的是資料型別預設值,而不是程式碼中被顯示賦予的值。

4.4 解析

  解析階段主要是虛擬機器將常量池中的符號轉化為直接引用的過程。

  

4.5 初始化

  這是類載入機制的最後一步,在這個階段,java 程式程式碼才真正執行。我們知道,在準備階段已經為類變數賦過一次值。在初始化階段,程式設計師可以根據自己的需要來賦值。

  在初始化階段,主要為類的靜態變數賦予正確的初始值,JVM 負責對類進行初始化,主要對類變數進行初始化。在 Java 中對類變數進行初始值設定有兩種方式:一種是宣告類變數指定初始值,另一種是使用靜態程式碼塊為類變數指定初始值

  JVM 初始化步驟:

  1)假如這個類還沒有被載入和連線,則程式先載入並連線該類

  2)假如該類的直接父類還沒有被初始化,則先初始化其直接父類

  3)假如類中有初始化語句,則系統依次執行這些初始化語句

  初始化時機:只有當對類的主動使用時候才會導致類的初始化,類的主動使用包括以下六種:

  1)建立類的例項,即 new

  2)訪問某個類或介面的靜態變數,或者對該靜態變數賦值

  3)呼叫類的靜態方法

  4)反射(如:Class.forName("com.mysql.jdbc.Driver"))

  5)初始化某個類的子類,則其父類也會被初始化

  6)Java 虛擬機器啟動時被標明為啟動類的類,直接使用 java.exe 命令來執行某個主類

5. 類載入器

  虛擬機器設計團隊把載入動作放到 JVM 外部實現,以便讓應用程式覺得如何獲取所需的類。

5.1 Java 語言系統自帶三個類載入器

  1)Bootstrap ClassLoader:最頂層的載入類,主要載入核心類庫,也就是我們環境變數下 %JAVA_HOME%/jre/lib 下的 rt.jar、resources.jar、charset.jar 和 class 等。另外需要注意的是可以通過啟動 jvm 時指定 -Xbootclasspath 和路徑來改變 Bootstrap ClassLoader 的載入目錄。比如 java -Xbootclasspath/a:path 被指定的檔案追加到預設的 bootstrap 路徑中。

  2)Extention ClassLoader:擴充套件的類載入器,載入目錄 %JRE_HOME%/lib/ext 目錄下的 jar 包和 class 檔案,還可以載入 -D java.ext.dirs 選項指定的目錄。

  3)App ClassLoader:也稱為 SystemAppClass。載入當前應用的 classpath 的所有類。

  

  java 為我們提供了三個類載入器,應用程式都是由這三種類載入器互相配合進行載入的,如果有必要,我們還可以加入自定義的類載入器。這三種類載入器的載入順序是什麼呢?

  Bootstrap ClassLoader > Extention ClassLoader > Appclass Loader

  

  ClassLoader loader = Thread.currentThread().getContextClassLoader();

  loader 是 App ClassLoader , loader.getParent() 是 Extention ClassLoader,再往上 getParent() 就是 null(Bootstrap ClassLoader 是 C語言實現的,獲取不到,所以返回 null)

5.2 類載入的三種方式

  1)通過命令列啟動應用時由 JVM 初始化載入含有 main() 方法的主類

  2)通過 Class.forName() 方法動態載入,會預設執行初始化塊(static{}),但是 Class.forName(name, initialize, loader) 中的 initialize 可以指定是否要執行初始化塊。

  3)通過 ClassLoader.loadClass() 方法動態載入,不會執行初始化塊

5.3 雙親委派原則

  當一個類載入器收到類載入任務時,會先交給其父類載入器去完成,因此最終載入任務都會傳遞到頂層的類載入器,只有當父類載入器無法完成載入任務時,才會嘗試執行載入任務。

  採用雙親委派的一個好處是,比如載入位於 rt.jar 包中的類 java.lang.Object,不管是哪個載入器載入這個類,最終都是委託給頂層的啟動類載入器進行載入,這樣就保證了使用不同的類載入器最終得到的都是同樣的一個 Object 物件。雙親委派原則歸納一下就是:

  1. 可以避免重複載入,父類已經載入了,子類就不需要再次載入

  2. 更加安全。很好的解決了各個類載入器的基礎類的統一問題,如果不使用該種方式,那麼使用者可以隨意定義類載入器來載入核心 api,會帶來相關隱患

5.4 自定義類載入器

  自定義類載入器有兩種方式:

  1)遵守雙親委派模型:繼承 ClassLoader,重寫 findClass() 方法

  2)破壞雙親委派模型:繼承 ClassLoader,重寫 loadClass() 方法。

  通常我們推薦採用第一種方法自定義類載入器,最大程度遵守雙親委派模型。

方法loadClass()丟擲的是java.lang.ClassNotFoundException異常;方法defineClass()丟擲的是java.lang.NoClassDefFoundError異常。

https://blog.csdn.net/qq_33543634/article/details/81128096