JVM類加載機(jī)制

在Java語言里,類的加載、連接和初始化都是在程序運(yùn)行期間完成的。這種方式雖然在性能上會一定的開銷,但是它會是Java的應(yīng)用程序具有更高的靈活性。

比如:

  • 我們在寫一個類中用了一個接口的方法,現(xiàn)在這個類編譯后的class中是不知道我們具體是哪個類實(shí)現(xiàn)的,只有等到了運(yùn)行程序的時候才指定了具體的實(shí)現(xiàn)類。
  • 另外我們可以通過先定義類加載器,然后我們可以隨時從任何地方加載一個二進(jìn)制流來動態(tài)的加載一個類。
  • 可以實(shí)現(xiàn)動態(tài)的替換jsp,還有OSGI的熱插拔技術(shù)

這些都是java提供給我們的可以用類加載機(jī)制來實(shí)現(xiàn)的功能

Java中類的生命周期

加載(Loading)、驗(yàn)證(Verification)、準(zhǔn)備(Preparation)、解析(Resolution)、初始化(Initialization)、使用(Using)、卸載(Unloading)

一個類的存在的順序大致會按照這個順序進(jìn)行,但是也會存在特殊的情況,在初始化的時候去解析

Java中什么情況下需要對類進(jìn)行初始化(階段),再此之前其他的操作:加載、驗(yàn)證等已經(jīng)完成

  1. 在使用new創(chuàng)建個對象時,或者使用這個類的靜態(tài)方法或者靜態(tài)變量(有一種情況除外,在靜態(tài)變量被final修飾的時候不會初始化對象,因?yàn)檫@種對象在編譯期間已經(jīng)把變量放在了常量池中了。)
  2. 在使用java.lang.reflect包中的方法,也就是在使用反射的時候會觸發(fā)初始化操作
  3. 初始化一個類的時候如果還沒有初始化父類的時候會初始化父類
  4. 啟動的時候會初始化執(zhí)行類的主類(Main方法)
  5. JDK1.7加上了動態(tài)語言支持,就是在遇到REF_getStatic、REF_putStatic、REF_invokeStatic的方法句柄時這個類如果沒有初始化才會觸發(fā)其初始化。(就是類似于Js、Python動態(tài)語言,不需要事先確定這個參數(shù)的類型,當(dāng)運(yùn)行到的時候在去,如果發(fā)現(xiàn)沒有初始化的話再去進(jìn)行初始化這個類)

加載階段

加載階段虛擬機(jī)主要做了這三件事情:

  1. 通過一個類的全限定名(包名+類名)來獲取定義此類的二進(jìn)制流(得到Class二進(jìn)制流)
  2. 把這個字節(jié)流鎖代表的經(jīng)他愛存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)結(jié)構(gòu)(把流轉(zhuǎn)化成方法區(qū)里能夠使用的數(shù)據(jù)結(jié)構(gòu))
  3. 在內(nèi)存中生成一個代表這個類的java.lang.Class對象,作為方法區(qū)這個類的各種數(shù)據(jù)的訪問入口(生成Class對象,注:Class對象比較特殊,在HotSpot虛擬機(jī)中它是在方法區(qū)內(nèi)不是在堆中)

這三個階段中可控性最強(qiáng)的就是第一個得到二進(jìn)制流的階段,我們可以通過各種方式來得到,比如從本地磁盤,從數(shù)據(jù)庫,從網(wǎng)絡(luò)上...等到流之后我們可以重寫一個類的加載器的loadClass()方法,或者是使用JDK提供的引導(dǎo)類加載器來完成。

對于數(shù)組和其他引用類型來說有些區(qū)別,數(shù)組本身是不通過類加載器創(chuàng)建的,它是由Java虛擬機(jī)自己直接創(chuàng)建的。但是數(shù)組類與類加載器還是有很多關(guān)系的,具體如下:

  • 如果數(shù)組的組件類型是引用類型(就是數(shù)組去掉第一個維度的類型),那么就是使用對應(yīng)的加載器加載組件類型,然后數(shù)組將會被組件類型的類加載器上被標(biāo)識。
  • 如果數(shù)組的組件類型不是引用類型(比如int等基本數(shù)據(jù)類型),java虛擬機(jī)將會吧數(shù)組C標(biāo)記與引導(dǎo)類加載器關(guān)聯(lián)
  • 數(shù)組類的可見性與它的組件類型的可見性是一致的,如果組件類型不是引用類型,那么這個數(shù)組類的可見性就是public(就是數(shù)組這個類的可見性和它里面的類的可見性是一致的)

驗(yàn)證階段

驗(yàn)證階段是為了確保Class文件的字節(jié)流中包含的信息是符合房錢虛擬機(jī)要求的。
雖然Java本身是相對安全的,因?yàn)樗芯幾g成Class這一步。編譯器是有一定規(guī)則的,如果你寫的不符合要求會拒絕編譯。但我們知道Class文件并不是只靠Java源碼編譯過來的,它可以是通過任何途徑獲得(如果你自己用十六進(jìn)制編輯器寫了一個十六進(jìn)制文件,只要符合要求也是可以運(yùn)行的)。如果虛擬機(jī)沒有檢查輸入的字節(jié)流那么可能會導(dǎo)致很嚴(yán)重的后果。(可能會有惡意的代碼,或者不是惡意的但會造成程序的崩潰)

大致上驗(yàn)證階段可以分為以下4個階段:

  • 文件格式驗(yàn)證
  • 元數(shù)據(jù)驗(yàn)證
  • 字節(jié)碼驗(yàn)證
  • 符號引用驗(yàn)證

準(zhǔn)備階段

準(zhǔn)備階段是正式為類變量分配內(nèi)存并設(shè)置類變量初始值的階段,這些都會在方法區(qū)內(nèi)分配。(這里的分配變量知識分配類變量,就是用static修飾的變量,不包括手里邊了,實(shí)例變量將會在對象實(shí)例化的時候進(jìn)行賦值。)
下圖是基本數(shù)據(jù)類型的初始值,引用類型的初始值是null


注意:上面說的是通常的情況,有一種情況是比較特殊的。就是在用final修飾了之后會在初始化階段直接初始化上ConstantValue的值。例如:

public static final int value =123;

這時候在編譯階段會吧value的值附上123了,準(zhǔn)備階段就自然會給value賦值成123

解析階段

在解析階段是把常量池中的符號引用轉(zhuǎn)化成直接引用,符號引用是在編譯成Class文件的時候生成的。
直接引用和符號引用之間的定義:

  • 符號引用:符號引用用一組符號聊描述所引用的目標(biāo),符號只需要沒有重復(fù)的能定位到目標(biāo)即可。這個目標(biāo)是不一定會加載到內(nèi)存中的,也就是說這時候目標(biāo)還不存在。
  • 直接引用:直接引用是指向目標(biāo)的指針、偏移量或者是句柄。直接引用和虛擬機(jī)內(nèi)存布局有關(guān)系。也就是說如果有了直接引用就說明這個目標(biāo)已經(jīng)在內(nèi)存存在了。

初始化階段

初始化階段必須是在前面的步驟都結(jié)束后才執(zhí)行的最后一步。初始化階段會真正的執(zhí)行類中定義的java程序代碼。
在初始化階段是執(zhí)行類構(gòu)造器方法的過程(就是執(zhí)行<clinit>類型的方法)。

  • <clinit>方法是由編譯器自動收集類中的所有類變量的賦值動作和靜態(tài)語句塊中語句合并產(chǎn)生的結(jié)果(static{})。這個執(zhí)行順序是由語句在源文件出現(xiàn)的順序所決定的。也就是說靜態(tài)語句塊只能訪問到定義在靜態(tài)語句塊之前的變量,但是定義在后邊的變量可以在前面的靜態(tài)語句塊中被復(fù)制。但是不能訪問
public class Test{
      static{
            i =0;                        
            System.out.print(i);           //這塊會有編譯錯誤非法向前引用
    }
    static int i = 1;
}
  • <clinit>方法與類的構(gòu)造函數(shù)不同,它不需要顯示的調(diào)用父類的構(gòu)造器,因?yàn)樘摂M機(jī)會保證在子類執(zhí)行之前父類的<clinit>方法一定會執(zhí)行成功。因此我們可以知道在虛擬機(jī)中第一個被執(zhí)行的<clinit>方法一定是java.lang.Object

  • 接口中是不能使用靜態(tài)語句塊的,但是可以有靜態(tài)變量賦值的操作。因此也會生成<clinit>方法。但接口月累不同的是執(zhí)行接口不需要先執(zhí)行父接口的方法,只有當(dāng)付借款使用時才會初始化。

  • 虛擬機(jī)會保證一個類的<clinit>方法會被正確的加鎖,也就是在多個線程初始化一個類時只會有一個線程去執(zhí)行這個類的方法。注意這種可能會造成阻塞(其他線程不會在重新執(zhí)行一次,一個類只會執(zhí)行一次)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

推薦閱讀更多精彩內(nèi)容

  • JVM類加載機(jī)制 概述 類加載過程 加載 通過類的全限定名獲取類的二進(jìn)制流 將靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)化為方法區(qū)的運(yùn)行時數(shù)據(jù)...
    東溪95閱讀 3,081評論 0 15
  • 簡述:虛擬機(jī)把描述類的數(shù)據(jù)從class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成可以被虛擬機(jī)直接...
    卡巴拉的樹閱讀 1,892評論 1 6
  • 概述 虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn),轉(zhuǎn)換,解析和初始化,最終形成可以被虛擬機(jī)直...
    lwd45閱讀 1,727評論 0 16
  • 簡介 代碼編譯的結(jié)果從本地機(jī)器碼轉(zhuǎn)變?yōu)樽止?jié)碼,是存儲格式發(fā)展的一小步,卻是編程語言發(fā)展的一大步。與那些在編譯時需要...
    黃俊彬閱讀 363評論 0 6
  • 一直以來從事的都是成熟的項目公司 所以這種賬號的申請一般不會有機(jī)會接觸到 慶幸的是現(xiàn)在入職一家軟件公司 獨(dú)立完成i...
    馬鈴薯蜀黍閱讀 957評論 3 1