JAVA類加載機(jī)制

概述

虛擬機(jī)把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存中,并對數(shù)據(jù)進(jìn)行驗(yàn)證,準(zhǔn)備,解析,初始化的一個(gè)過程,最終是可以被虛擬機(jī)直接使用的java類型,這就是類加載的一個(gè)簡單的過程。
Java中的類加載是在運(yùn)行時(shí)加載,這樣會比較的消耗性能,但是正是在運(yùn)行時(shí)加載使得java擁有很好的靈活性和可擴(kuò)展性。

類加載的時(shí)機(jī)

類從被加載到內(nèi)存中開始,到卸載出內(nèi)存為止。它的生命周期總共七個(gè)階段:加載---->驗(yàn)證---->準(zhǔn)備---->解析---->初始化---->使用---->卸載。其中解析這個(gè)過程是不確定的,它可能會在初始化后之后,這是為了使java支持運(yùn)行時(shí)的綁定。

  • new ,getstatic,putstatic,invokestatic這四條指令時(shí)會觸發(fā)初始化的操作。
  • new是new一個(gè)新的對象時(shí)會觸發(fā)初始化。
  • getstatic是獲取靜態(tài)字段時(shí)會觸發(fā)。
  • putstatic是設(shè)置靜態(tài)字段時(shí)會觸發(fā)。
  • invokestatic是調(diào)用另一個(gè)類的靜態(tài)方法的時(shí)候。
    PS:需要注意的是getstatic和putstatic被final修飾的,在編譯期就放入到常量池中是不會觸發(fā)的。
  • 使用java.lang.reflect的包方法對類進(jìn)行反射調(diào)用時(shí),如果類沒有初始化就需要進(jìn)行初始的操作。
  • 子類進(jìn)行初始化時(shí)需要對父類先進(jìn)行初始。
  • java啟動(dòng)時(shí)需要的啟動(dòng)主類,程序的入口。該類就需要進(jìn)行初始化。
  • 使用JDK1.7的動(dòng)態(tài)語言支持時(shí),如果一個(gè)java.lang.invoke.Methondhandle實(shí)例最后解析結(jié)果REF_getStatic,REF_putStatic,REF_invokeStatic的方法句柄,如果沒有進(jìn)行初始化時(shí)會觸發(fā)初始化。
    PS:接口的初始化和類初始化不同,接口初始化只和類初始化的子類初始化是需要父類先進(jìn)行初始化,而且并不是接口父類中的所有都是會初始化。

加載

加載是類加載中前面提到的其中的一個(gè)過程。類加載的基本過程:

  • 通過全限定類名加載二進(jìn)制流。
  • 將二進(jìn)制流代表的靜態(tài)存儲結(jié)構(gòu)轉(zhuǎn)換方法區(qū)中運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)。
  • 在內(nèi)存中生成java.lang.Class對象,將這個(gè)作為該方法區(qū)這個(gè)類中各種數(shù)據(jù)的一個(gè)入口。

加載分為數(shù)組類加載過程和非數(shù)組類的加載過程。java的數(shù)組類的加載過程其實(shí)是有虛擬機(jī)直接加載的但是數(shù)組中的類型需要類加載機(jī)制加載:

  • 非數(shù)組類加載機(jī)制:可控性強(qiáng)既可以有系統(tǒng)類加載器進(jìn)行加載又可以由用戶自定義的類加載器進(jìn)行加載。(重寫一個(gè)類加載器的loadClass()方法)。
  • 數(shù)組類型的加載機(jī)制:數(shù)組類型的加載機(jī)制如果是引用類型,就使用遞歸進(jìn)行加載,并且會在加載的類型上加入一個(gè)標(biāo)志。如果是非引用類型則會把標(biāo)志與引導(dǎo)類加載器關(guān)聯(lián)。

ps:數(shù)組類的可見性與它組件的可見性是相同的,如果組件類型不是引用類型的可見性一般設(shè)置為public。
類加載完成會有一個(gè)連接,可能在沒完成加載就開始連接,雖然如此但是該順序是一定的。

驗(yàn)證

驗(yàn)證的主要目的是保證加載進(jìn)來的Class文件的字節(jié)流包含的信息符合虛擬機(jī)的當(dāng)前的要求,不會有危害自身的數(shù)據(jù)存在。
Java是相對C++語言是安全的語言,例如它有C++不具有的數(shù)組越界的檢查。這本身就是對自身安全的一一種保護(hù)。驗(yàn)證階段是Java非常重要的一個(gè)階段,它會直接的保證應(yīng)用是否會被惡意入侵的一道重要的防線,越是嚴(yán)謹(jǐn)?shù)尿?yàn)證機(jī)制越安全。驗(yàn)證的四個(gè)階段文件格式驗(yàn)證-->元數(shù)據(jù)驗(yàn)證-->字節(jié)碼驗(yàn)證-->符號引用驗(yàn)證。

  • 文件格式驗(yàn)證:主要驗(yàn)證字節(jié)流是否符合Class文件格式規(guī)范,并且能被當(dāng)前的虛擬機(jī)加載處理。
  • 是否以魔數(shù)開頭。
  • 主,次版本號是否在當(dāng)前虛擬機(jī)處理的范圍之內(nèi)。
  • 常量池中是否有不被支持的常量類型。
  • 指向常量的中的索引值是否存在不存在的常量或不符合類型的常量。
  • CONSTANT_Utf8_info型的常量中有不符合utf8格式的編碼數(shù)據(jù)。
    還有大其它的驗(yàn)證這里就不一一的列舉。
  • 元數(shù)據(jù)驗(yàn)證:對字節(jié)碼描述的信息進(jìn)行語義的分析,分析是否符合java的語言語法的規(guī)范。
  • 字節(jié)碼驗(yàn)證:最重要的驗(yàn)證環(huán)節(jié),分析數(shù)據(jù)流和控制,確定語義是合法的,符合邏輯的。主要的針對元數(shù)據(jù)驗(yàn)證后對方法體的驗(yàn)證。保證類方法在運(yùn)行時(shí)不會有危害出現(xiàn)。
  • 符號引用驗(yàn)證:主要是針對符號引用轉(zhuǎn)換為直接引用的時(shí)候,是會延伸到第三解析階段,主要去確定訪問類型等涉及到引用的情況,主要是要保證引用一定會被訪問到,不會出現(xiàn)類等無法訪問的問題。

雖然驗(yàn)證很重要但是并不是必須的階段。當(dāng)然大量重復(fù)的驗(yàn)證會相當(dāng)?shù)幕ㄙM(fèi)性能和時(shí)間的。
準(zhǔn)備


準(zhǔn)備階段主要是類變量進(jìn)行分配內(nèi)存和數(shù)據(jù)的初始化階段,所謂的初始化并不是你編碼時(shí)所定義的變量值。例如:

public static int age = 20;

數(shù)據(jù)的初始化并不會將它初始化為20,而是初始化為0,系統(tǒng)有一套自己的初始化值。如下圖:

數(shù)據(jù)類型 零值
int 0
long 0L
short 0
char '\u0000'
byte 0
boolean false
float 0.0f
double 0.0d
reference null

當(dāng)然會有特殊的情況,如下面的代碼:

public static final int value = 20;

這種情況是類的字段時(shí)存在ConstantValue屬性所指定的字段。用final修飾后出現(xiàn)該屬性,加初始化時(shí)會直接的使用ConstantValue的屬性值,所以會初始化為20。
解析


解析是將常量池中的符號引用轉(zhuǎn)化為直接引用的過程,還記得前面驗(yàn)證階段時(shí)出現(xiàn)的符號引用驗(yàn)證嗎?就是對該階段的驗(yàn)證。

  • 符號引用:符號引用是以一組符號來描述所引用的目標(biāo),符號可以是任何的字面形式的字面量,只要不會出現(xiàn)沖突能夠定位到就行。布局和內(nèi)存無關(guān)。
  • 直接引用:是指向目標(biāo)的指針,偏移量或者能夠直接定位的句柄。該引用是和內(nèi)存中的而布局有關(guān)的,并且一定加載進(jìn)來的。

虛擬機(jī)可能會多次的進(jìn)行解析。解析主要的對類,接口,字段,類方法,接口方法,方法類型,方法句柄和調(diào)用點(diǎn)限定符引用進(jìn)行。這七種解析有細(xì)節(jié)上的不同,主要的思想是通過限定性類名找到解析的類型進(jìn)行解析。主要的是會分為數(shù)組類型,非數(shù)組類型存在一個(gè)直接進(jìn)行解析的過程。在過程還有從下上的匹配查找(主要出現(xiàn)在有繼承,接口的情況下)。

初始化

初始化算是類加載過程的最后一個(gè)階段,在這個(gè)階段在是真正的開始有java代碼主導(dǎo)。大家應(yīng)該記得在準(zhǔn)備階段已經(jīng)進(jìn)行過一次賦值,但是只是系統(tǒng)的默認(rèn)賦值(ConstantValue的例外情況)。初始化是執(zhí)行<clinit>的過程。

  • <clinit>的主要是查找static模塊,用戶自定義類變量的賦值,該順序是由文件中的順序界定的。加載過程存在的是父類的一定會比子類先進(jìn)行加載到,因?yàn)闀WC子類的<clinit>加載完成時(shí)父類的<clinit>一定會加載完成。所有就像大家所知道的java.lang.object一定會是虛擬機(jī)中第一個(gè)加載完成的。
  • <clinit>在接口中的加載是不同的它是不存在靜態(tài)塊的,接口中也是會有賦值進(jìn)行的,但是接口中的是在需要用到才會去進(jìn)行加載的。
  • 允許在定義之前進(jìn)行賦值的操作,但是不允許使用,如下:
public class A{
  static{
          s = 20;
          //system.out.printf(s); 
          上面注釋的這句話時(shí)會出現(xiàn)錯(cuò)誤的;
  }
  static int s = 10;
}  
  • 虛擬機(jī)會保證在多線程的環(huán)境下進(jìn)行加鎖,保證正確執(zhí)行。如果有多個(gè)進(jìn)行加載一個(gè)會保證只有一個(gè)去加載,其他的會進(jìn)去阻塞等待中。同一個(gè)類只會加載一次,就算多個(gè)進(jìn)入阻塞也不會重新喚醒。

類加載器

  • 類與類加載器:一個(gè)類的相同判斷條件大家都知道,但是如果不是由同一個(gè)類加載器加載出來的,就算是看起來相同的也是出現(xiàn)false的。
  • 三大類加載器:
    • 啟動(dòng)類加載器
    • 擴(kuò)展類加載器
    • 應(yīng)用程序類加載器
  • 雙親委托機(jī)制:


    雙器委托機(jī)制

    雙親委托機(jī)制是當(dāng)一個(gè)類進(jìn)入加載時(shí),子加載器不會自己嘗試去加載,而是將其發(fā)送到它的父加載器中加載,以此類推直到達(dá)到最后的加載器,只有當(dāng)父加載器不能進(jìn)行加載是會發(fā)送到子加載器中,子加載此時(shí)才會嘗試去加載。

public Class<?> loadClass(String name)throws ClassNotFoundException {
            return loadClass(name, false);
    }
 
    protected synchronized Class<?> loadClass(String name, boolean resolve)throws ClassNotFoundException {
            // 首先判斷該類型是否已經(jīng)被加載
            Class c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                    //如果不存在父類加載器,就檢查是否是由啟動(dòng)類加載器加載的類,通過調(diào)用本地方法native Class findBootstrapClass(String name)
                        c = findBootstrapClass0(name);
                    }
                } catch (ClassNotFoundException e) {
                 // 如果父類加載器和啟動(dòng)類加載器都不能完成加載任務(wù),才調(diào)用自身的加載功能
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
  • 雙親委托機(jī)制的破壞
  • 1.2版本為了向前兼容1.0版本
  • 本身模型的問題,基礎(chǔ)類要調(diào)用用戶類而出現(xiàn)的沖突。通過設(shè)置線程上下文類加載器,如果出現(xiàn)上面這種情況,通過上下文類加載器去加載所需的類。
  • 用戶對動(dòng)態(tài)性的追求,出現(xiàn)沒一個(gè)模塊都有自己的類加載器,如果需要更換時(shí)連同類加載器一同換掉。
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • Java的核心是 JVM ,了解并熟悉JVM對于我們理解Java語言非常重要。 一、類加載機(jī)制 當(dāng)程序主動(dòng)使用某個(gè)...
    年少懵懂丶流年夢閱讀 1,161評論 2 15
  • 1.虛擬機(jī)如何加載這些Class文件?(類加載的過程)2.Class文件中的信息進(jìn)入到虛擬機(jī)后會發(fā)生什么變化? J...
    wangcanfeng閱讀 267評論 0 0
  • 類加載器簡單來說是用來加載 Java 類到 Java 虛擬機(jī)中的。Java 虛擬機(jī)使用 Java 類的方式如下:J...
    愛情小傻蛋閱讀 772評論 2 11
  • 一、類加載機(jī)制 1.定義: 把描述類的數(shù)據(jù)從Class文件加載到內(nèi)存,并對數(shù)據(jù)進(jìn)行校驗(yàn)、轉(zhuǎn)換解析和初始化,最終形成...
    Ruheng閱讀 2,687評論 6 36
  • 虛擬機(jī)類加載機(jī)制 1. 類加載的時(shí)機(jī)1.1 類從被加載到虛擬機(jī)內(nèi)存開始,到卸載出內(nèi)存為止,他的整個(gè)生命周期包括: ...
    天空在微笑閱讀 227評論 0 0

友情鏈接更多精彩內(nèi)容