JVM 類加載機制及雙親委派模型

概述java

java虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗、轉換解析和初始化,最終造成能夠被虛擬機直接使用的java類型,這就是虛擬機的類加載機制。
 
  上面這段是《深刻理解java虛擬機》中對類加載的描述,其實簡單點說就是程序從最開始的.java文件到.Class文件,Class文件中包java虛擬機指令集和符號表以及其餘輔助信息,而這些信息最終加載到虛擬機才能被使用。接下來咱們就一塊兒討論這些Class文件如何被加載以及被加載後變成了什麼。
複製代碼

類的生命週期數據庫

如上圖所示,描述了類的生命週期。其中加載、驗證、準備、初始化、卸載這五個動做是存在前後順序的,而解析階段有可能在初始化以後完成的。這些動做中一般都是互相交叉混合進行的。下面咱們主要探討加載、驗證、準備、解析、初始化這五個步驟。

加載

  • 什麼時候加載安全

    1.預加載:在虛擬機啓動的時候加載,加載的是JAVA_HOME/lib/下的rt.class下的.class文件,是java程序運行時常常要用到的一些類,好比java.lang.⁎以及 java.util.⁎等
    2.運行時加載:虛擬機在用到一個.class文件時,首先會去內存中查找這個.class文件有沒有被加載,沒有被加載會根據這個類的全限定名去加載。
    複製代碼
  • 加載階段虛擬機作了什麼網絡

    1. 經過一個類的全限定名獲取定義此類的二進制字節流。
    2. 將這個字節流所表明的靜態存儲結構轉化爲方法區的運行數據結構。
    3. 在內存中生成一個惟一表明此類的java.lang.Class對象,做爲方法區中這個類的各類數據的訪問入口。(通常這個class對象會存儲在堆中,不過HotSpot虛擬機比較特殊,這個Class對象是放在方法區中的。)
    複製代碼
虛擬機對上述三點的要求並不算具體,例如第一條,根本沒指明二進制字節流從哪裏來,怎麼來,包括如下幾點:
     從zip包中獲取,這就是之後jar、ear、war格式的基礎
     從網絡中獲取,典型應用就是Applet
     運行時計算生成,典型應用就是動態代理技術
     由其餘文件生成,典型應用就是JSP,即由JSP生成對應的.class文件
     從數據庫中讀取,這種場景比較少見...
複製代碼

驗證

顧名思義,是對Class文件字節流的驗證,而驗證的目的則是爲了確保當前的Class文件符合java虛擬機的要求,而且不會危害虛擬機自身的安全。一般主要包括如下幾點驗證內容:
   1. 文件格式驗證
      其實就是驗證字節流是否符合Class文件規範,符合規範經過驗證才能保證輸入的字節流能正確的被解析並存儲到方法區。
   2. 元數據驗證
      對類的元數據信息進行語義校驗。
   3. 字節碼驗證
    最爲複雜的校驗階段,校驗程序語義是否符合規範,符合邏輯,對類的方法體進行校驗。  
   4. 符號引用驗證
     發生在將符號引用轉換爲直接引用的時候,能夠看作是對類自身之外(常量池中各類符號的應用)信息的匹配校驗,如:符號引用中經過字符串描述的全限定名是否能找到對應的類;符號引用中的類、字段、方法的訪問性(private、protected、public、default)是否可被 當前類訪問...
複製代碼

準備

正式爲類變量分配內存並賦初值。
 須要注意兩點
   1. 只爲類變量,即被static修飾的變量分配內存,實例變量在實例初始化的時候會隨對象一塊兒分配在堆中。
   2. 這個階段賦初始值的變量指的是那些不被final修飾的static變量,好比」public static int value = 123;」,value在準備階段事後是0而不是123,給value賦值爲123的動做將在初始化階段才進行;好比」public static final int value = 123;」就不同了,在準備階段,虛擬機就會給value賦值爲123。
   基本數據的零值以下表:
複製代碼

解析

解析是虛擬機將常量池中的符號引用轉換爲直接引用的過程。
   1. 符號引用
   這個實際上是屬於編譯原理方面的概念,符號引用包括了下面三類常量:
     類和接口的全限定名
     字段的名稱和描述符
     方法的名稱和描述符
複製代碼

看到Constant Pool也就是常量池中有22項內容,其中帶」Utf8″的就是符號引用。好比#2,它的值是」com/xrq/test6/TestMain」,表示的是這個類的全限定名;又好比#5爲i,#6爲I,它們是一對的,表示變量時Integer(int)類型的,名字叫作i;#6爲D、#7爲d也是同樣,表示一個Double(double)類型的變量,名字爲d;#1八、#19表示的都是方法的名字。

那其實總而言之,符號引用和咱們上面講的是同樣的,是對於類、變量、方法的描述。符號引用和虛擬機的內存佈局是沒有關係的,引用的目標未必已經加載到內存中了。數據結構

2. 直接引用
     直接引用能夠是直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用是和虛擬機實現的內存佈局相關的,同一個符號引用在不一樣的虛擬機示例上翻譯出來的直接引用通常不會相同。若是有了直接引用,那引用的目標一定已經存在在內存中了。
複製代碼

初始化

開始真正的執行類中定義的java代碼,初始化過程就是執行類構造器<clinit>()的過程,還記得以前的準備階段是給類變量分配內存賦初值,這裏就是將類變量賦予用戶指定的值
 。
 虛擬機規範定義了「有且僅有」5中會觸發初始化的場景:
    1. 遇到new、getstatic、putstatic或invokestatic這四條字節碼指令時,若是累沒有進行初始化,則要先觸發初始化;
    2. 使用java.lang.reflect包中的方法對類進行反射調用的時候;
    3. 初始化類時,若發現其父類尚未初始化,則先觸發父類的初始化;
    4. 虛擬機啓動的時候,虛擬機會先初始化用戶指定的包含main()方法的那個類
    5. 當使用JDK 1.7的動態語言支持時,若是一個java.lang.invoke.MethodHandle實例最後的解析結果REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄,而且這個方法句柄所對應的類沒有進行過初始化,則須要先觸發其初始化。
複製代碼

雙親委派模型

介紹雙親委派模型以前先說下類加載器。對於任意一個類,都須要由加載它的類加載器和這個類自己一同確立在jvm中的惟一性,每個類加載器,都有一個獨立的類名稱空間。類加載器就是根據指定全限定名稱將class文件加載到JVM內存,轉爲Class對象。
 從jvm角度來看只存在兩種類加載器
複製代碼
  • 啓動類加載器(Bootstrap ClassLoader),是虛擬機自身的一部分,用來加載JAVA_HOME/lib/目錄中的,或者被-Xbootclasspath參數所指定的路徑中而且被虛擬機識別的類庫。jvm

  • 其餘類加載器:由Java語言實現,繼承自抽象類ClassLoader:模塊化

    1. 擴展類加載器(Extension ClassLoader):負責加載<JAVA_HOME>\lib\ext目錄或java.ext.dirs系統變量指定的路徑中的全部類庫。
    2. 應用程序類加載器(Application ClassLoader)。負責加載用戶類路徑(classpath)上的指定類庫,咱們能夠直接使用這個類加載器。通常狀況,若是咱們沒有自定義類加載器默認就是用這個加載器。

雙親委派模型的工做過程是:若是一個類加載器收到了類加載的請求,它首先不會本身去加載這個類,而是把這個請求委派給父類加載器去完成,每一層的類加載器都是如此,這樣全部的加載請求都會被傳送到頂層的啓動類加載器中,只有當父加載沒法完成加載請求(它的搜索範圍中沒找到所需的類)時,子加載器纔會嘗試去加載類。下面舉一個你們都知道的例子說明爲何要使用雙親委派模型。函數

黑客自定義一個java.lang.String類,該String類具備系統的String類同樣的功能,只是在某個函數稍做修改。好比equals函數,這個函數常用,若是在這這個函數中,黑客加入一些「病毒代碼」。而且經過自定義類加載器加入到JVM中。此時,若是沒有雙親委派模型,那麼JVM就可能誤覺得黑客自定義的java.lang.String類是系統的String類,致使「病毒代碼」被執行。佈局

而有了雙親委派模型,黑客自定義的java.lang.String類永遠都不會被加載進內存。由於首先是最頂端的類加載器加載系統的java.lang.String類,最終自定義的類加載器沒法加載java.lang.String類。spa

或許你會想,我在自定義的類加載器裏面強制加載自定義的java.lang.String類,不去經過調用父加載器不就行了嗎?確實,這樣是可行。可是,在JVM中,判斷一個對象是不是某個類型時,若是該對象的實際類型與待比較的類型的類加載器不一樣,那麼會返回false。

舉個簡單例子:

ClassLoader一、ClassLoader2都加載java.lang.String類,對應Class一、Class2對象。那麼Class1對象不屬於ClassLoad2對象加載的java.lang.String類型。

如何實現雙親委派模型

雙親委派模型的原理很簡單,實現也簡單。每次經過先委託父類加載器加載,當父類加載器沒法加載時,再本身加載。其實ClassLoader類默認的loadClass方法已經幫咱們寫好了,咱們無需去寫。
複製代碼

破壞雙親委派模型

雙親委派模型並非一個強制性約束,而是java設計者推薦給開發者的類加載器的實現方式,在必定條件下,爲了完成某些操做,能夠「破壞」模型。
     1.從新loadClass方法
     2.利用線程上下文加載器(Thread Context ClassLoader)。這個類加載器能夠經過java.lang.Thread類的 setContextClassLoaser()方法進行設置,若是建立線程時還未設置,它將會從父線程中繼承 一個,若是在應用程序的全局範圍內都沒有設置過的話,那這個類加載器默認就是應用程序 類加載器。
     3.爲了實現熱插拔,熱部署,模塊化,意思是添加一個功能或減去一個功能不用重啓,只須要把這模塊連同類加載器一塊兒換掉就實現了代碼的熱替換。
複製代碼

本文參考《深刻理解java虛擬機》

[深度分析Java的ClassLoader機制(源碼級別)](http://www.hollischuang.com/archives/199)

[Java類的加載、連接和初始化](http://www.hollischuang.com/archives/201)

相關文章
相關標籤/搜索