1. 程式人生 > 實用技巧 >JVM之類載入子系統

JVM之類載入子系統

JVM之類載入器

所在位置

詳細圖

類載入子系統作用

  • 類載入子系統負責從檔案系統或者網路中載入class檔案,class檔案在檔案開頭有特定的檔案標識、
  • ClassLoader只負責class檔案的載入,至於它是否可以執行,則由Execution Egine(執行引擎)來決定。
  • 載入的類資訊存放於一塊稱為方法區的記憶體空間。除了類資訊外,方法區還會存放執行時常量池資訊,可能還包含字串字面量和數字常量(這部分常量資訊是class檔案中常量池部分的記憶體對映)

ClassLoad角色

  • class file存在於本地硬碟中,可以理解為設計師畫在紙上的模板,而最終這個模板在執行的時候是要載入到JVM當中,根據這個檔案例項化出n個一模一樣的例項。
  • class file載入到JVM中,被稱為NDA元資料模板,放在方法區。
  • 在·.class·檔案 -> JVM -> 最終資料模板,這個過程就是ClassLoader要做的事情。

類的載入過程

載入(Loading)

  • 1、通過一個類的全限定名獲取此類的二進位制位元組流
  • 2、將這個位元組流所代表的靜態儲存結構轉化為方法區執行時資料結構
  • 3、在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料訪問入口

連結(Linking)

驗證(Verify)

  • 目的在於確保class檔案中位元組流中包含資訊符合當前虛擬機器要求,保證被載入類的正確性,不會危害虛擬機器自身安全。
  • 主要包括4中驗證:檔案格式驗證、元資料驗證、位元組碼驗證、符號引用驗證

準備(Prepare)

  • 為類變數分配記憶體並且設定該變數的預設初始值,即零值。
  • 這裡不包含用final修飾的static,因為final在編譯的時候就已經分配了,準備階段會顯式初始化。
  • 這裡不會為例項變數分類初始化,類變數會分配在方法區中,而例項變數是會隨著物件一起分配到Java堆中。

解析(Resolve)

  • 將常量池內的符號引用轉換為直接引用的過程
  • 事實上,解析操作往往會伴隨著JVM在執行完初始化後在執行。
  • 符號引用就是一組符號來描述引用的目標。符號引用的字面量形式明確定義在《Java虛擬機器規範》的Class
    檔案格式中。直接引用就是直接指向目標的指標,相對偏移量或者一個間接定位到目標的控制代碼。
  • 解析動作主要針對類或介面。欄位、類方法、介面方法、方法型別等。對常量池中的CONSTANT_Class_infoCONSTANT_Fieldref_infoCONSTANT_Methodef_info等

初始化(Initialization)

  • 初始化階段就是指向類構造器方法()的過程
  • 此方法不需要定義,是javac編譯器自動收集類中的所有類變數的賦值動作和靜態程式碼塊中的語句合併而來的。
  • 構造器方法中指令按照語句在原始檔中出現的順序執行。
  • ()不同於類構造器(構造器是虛擬機器視角下的())
  • 若該類具有父類,JVM會保證子類的()執行前,父類的(已經執行完畢)
  • 虛擬機器必須保證一個類的()方法在多執行緒下被同步加鎖。

類載入器分類

啟動類載入器(引導類載入器 Bootstrap ClassLoader)

  • 這個類載入器使用c/c++語言實現,巢狀在JVM內部。
  • 它用來載入Java的核心庫(JAVA_HOME/jar/lib/rt.jar、resources.jar、sun.boot.class.path路徑下的內容),用於提供JVM自身需要的類
  • 並不繼承自java.lang.ClassLoader,沒有父載入器。
  • 夾雜擴充套件類和應用程式類記載其,並指定為它們的父類載入器。
  • 出於安全考慮,Bootstrap啟動類載入器只加載包名為javajavaxsun等開頭的類

擴充套件類載入器(Extension ClassLoader)

  • Java語言編寫,由sun.misc.Launcher$ExtClassLoader實現。
  • 派生於ClassLoader
  • java.ext.dirs系統屬性所指定的目錄中載入類庫,或從JDK的安裝包目錄jar/lib/ext子目錄下下載類庫。如果用建立的JAR放在此目錄下,也會自動有擴充套件類載入器載入。

應用程式載入器(系統類載入器 AppClassLoader)

  • java 語言編寫,由sun.misc.Launcher$AppClassLoader實現
  • 派生與ClassLoader
  • 它負責載入環境變數classpath或者系統屬性java.class.path指定路徑下的類庫
  • 該類載入器是程式中預設的載入器,一般來說,Java應用的類都是由它完成載入
  • 通過ClassLoader#getSystemClassLoader()方法可以獲取該類載入器。

檢視類載入器可載入的路徑

package top.mgy.classloader;

import java.net.URL;

import static sun.misc.Launcher.getBootstrapClassPath;

public class ClassLoaderTest {
    public static void main(String[] args) {
        System.out.println("*****啟動類載入器******");
        //獲取BootstrapClassLoader能載入的類庫路徑
        URL[] urLs = getBootstrapClassPath().getURLs();
        for (URL url : urLs) {
            System.out.println(url.toExternalForm());
        }

        System.out.println("*******擴充套件類載入器********");
        String extDirs = System.getProperty("java.ext.dirs");
        for (String path:extDirs.split(";")){
            System.out.println(path);
        }
    }
}

使用者自定義類載入器

在Java的日常應用程式開發中,類載入器幾乎由上述3中載入器相互配合執行的,在必要時,我們可以自定義類載入器,來定製類的載入方式。

為什麼要自定義類載入器?

  • 隔離載入類
  • 修改類的載入方式
  • 擴充套件載入源
  • 防止原始碼洩露

如何自定義類載入器?

獲取ClassLoader幾種途徑

雙親委派機制

Java虛擬機器對Class檔案採用的是按需載入的方式,也就是說當需要使用該類時才會將它的class檔案載入到記憶體生成class物件。而且載入某個類的class檔案時,Java虛擬機器採用的是雙親委派模式,即把請求交由父類處理,它是一種任務委派模式

工作原理

  • 1、如果一個類載入器收到了類載入請求,它並不會自己先去載入,而是把這個請求委託給父類的載入器去執行
  • 2、如果父類載入還存在父類載入器,則進一步向上委託,依次遞迴,請求最終價格到達頂層的啟動類載入器。
  • 3、如果父類載入器可以完成載入任務,就成功返回,若父類載入器無法完成載入任務,子載入器才會嘗試自己去載入,這就是雙親委派模式。

雙親委派機制的優勢

  • 避免類被重複載入

  • 保護程式安全,防止核心API被隨意篡改

    自定義類java.lang.String

    自定義類java.lang.Haha

沙箱安全機制

自定義String類,在載入自定義String類的時候會先使用引導類載入器載入,而引導類載入器在載入的過程中會先載入jdk自帶的檔案,報錯資訊說沒有main方法就是因為載入的是rt.jar包中String類。這樣可以保證對java核心原始碼的保護,這就是沙箱安全機制。