Java類加載器原理

每個編寫的”.java”拓展名類文件都存儲著需要執(zhí)行的程序邏輯,這些”.java”文件經(jīng)過Java編譯器編譯成拓展名為”.class”的文件,”.class”文件中保存著Java代碼經(jīng)轉(zhuǎn)換后的虛擬機指令,當(dāng)需要使用某個類時,虛擬機將會加載它的”.class”文件,并創(chuàng)建對應(yīng)的class對象,將class文件加載到虛擬機的內(nèi)存,這個過程稱為類加載,這里我們需要了解一下類加載的過程,如下:

image.png

加載:類加載過程的一個階段:通過一個類的完全限定查找此類字節(jié)碼文件,并利用字節(jié)碼文件創(chuàng)建一個Class對象

驗證:目的在于確保Class文件的字節(jié)流中包含信息符合當(dāng)前虛擬機要求,不會危害虛擬機自身安全。主要包括四種驗證,文件格式驗證,元數(shù)據(jù)驗證,字節(jié)碼驗證,符號引用驗證。

準備:為類變量(即static修飾的字段變量)分配內(nèi)存并且設(shè)置該類變量的初始值即0(如static int i=5;這里只將i初始化為0,至于5的值將在初始化時賦值),這里不包含用final修飾的static,因為final在編譯的時候就會分配了,注意這里不會為實例變量分配初始化,類變量會分配在方法區(qū)中,而實例變量是會隨著對象一起分配到Java堆中。

解析:主要將常量池中的符號引用替換為直接引用的過程。符號引用就是一組符號來描述目標(biāo),可以是任何字面量,而直接引用就是直接指向目標(biāo)的指針、相對偏移量或一個間接定位到目標(biāo)的句柄。有類或接口的解析,字段解析,類方法解析,接口方法解析(這里涉及到字節(jié)碼變量的引用,如需更詳細了解,可參考《深入Java虛擬機》)。

初始化:類加載最后階段,若該類具有超類,則對其進行初始化,執(zhí)行靜態(tài)初始化器和靜態(tài)初始化成員變量(如前面只初始化了默認值的static變量將會在這個階段賦值,成員變量也將被初始化)。

這便是類加載的5個過程,而類加載器的任務(wù)是根據(jù)一個類的全限定名來讀取此類的二進制字節(jié)流到JVM中,然后轉(zhuǎn)換為一個與目標(biāo)類對應(yīng)的java.lang.Class對象實例,在虛擬機提供了3種類加載器,引導(dǎo)(Bootstrap)類加載器、擴展(Extension)類加載器、系統(tǒng)(System)類加載器(也稱應(yīng)用類加載器),下面分別介紹

啟動(Bootstrap)類加載器

啟動類加載器主要加載的是JVM自身需要的類,這個類加載使用C++語言實現(xiàn)的,是虛擬機自身的一部分,它負責(zé)將 <JAVA_HOME>/lib路徑下的核心類庫或-Xbootclasspath參數(shù)指定的路徑下的jar包加載到內(nèi)存中,注意必由于虛擬機是按照文件名識別加載jar包的,如rt.jar,如果文件名不被虛擬機識別,即使把jar包丟到lib目錄下也是沒有作用的(出于安全考慮,Bootstrap啟動類加載器只加載包名為java、javax、sun等開頭的類)。

擴展(Extension)類加載器

擴展類加載器是指Sun公司(已被Oracle收購)實現(xiàn)的sun.misc.Launcher$ExtClassLoader類,由Java語言實現(xiàn)的,是Launcher的靜態(tài)內(nèi)部類,它負責(zé)加載<JAVA_HOME>/lib/ext目錄下或者由系統(tǒng)變量-Djava.ext.dir指定位路徑中的類庫,開發(fā)者可以直接使用標(biāo)準擴展類加載器。

系統(tǒng)(System)類加載器

也稱應(yīng)用程序加載器是指 Sun公司實現(xiàn)的sun.misc.Launcher$AppClassLoader。它負責(zé)加載系統(tǒng)類路徑j(luò)ava -classpath或-D java.class.path 指定路徑下的類庫,也就是我們經(jīng)常用到的classpath路徑,開發(fā)者可以直接使用系統(tǒng)類加載器,一般情況下該類加載是程序中默認的類加載器,通過ClassLoader#getSystemClassLoader()方法可以獲取到該類加載器。
  在Java的日常應(yīng)用程序開發(fā)中,類的加載幾乎是由上述3種類加載器相互配合執(zhí)行的,在必要時,我們還可以自定義類加載器,需要注意的是,Java虛擬機對class文件采用的是按需加載的方式,也就是說當(dāng)需要使用該類時才會將它的class文件加載到內(nèi)存生成class對象,而且加載某個類的class文件時,Java虛擬機采用的是雙親委派模式即把請求交由父類處理,它一種任務(wù)委派模式,下面我們進一步了解它。

雙親委派模式工作原理

雙親委派模式要求除了頂層的啟動類加載器外,其余的類加載器都應(yīng)當(dāng)有自己的父類加載器,請注意雙親委派模式中的父子關(guān)系并非通常所說的類繼承關(guān)系,而是采用組合關(guān)系來復(fù)用父類加載器的相關(guān)代碼,類加載器間的關(guān)系如下:


image.png

雙親委派模式是在Java 1.2后引入的,其工作原理的是,如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器去執(zhí)行,如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞歸,請求最終將到達頂層的啟動類加載器,如果父類加載器可以完成類加載任務(wù),就成功返回,倘若父類加載器無法完成此加載任務(wù),子加載器才會嘗試自己去加載,這就是雙親委派模式,即每個兒子都很懶,每次有活就丟給父親去干,直到父親說這件事我也干不了時,兒子自己想辦法去完成,這不就是傳說中的實力坑爹啊?那么采用這種模式有啥用呢?
采用雙親委派模式的是好處是Java類隨著它的類加載器一起具備了一種帶有優(yōu)先級的層次關(guān)系,通過這種層級關(guān)可以避免類的重復(fù)加載,當(dāng)父親已經(jīng)加載了該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設(shè)通過網(wǎng)絡(luò)傳遞一個名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發(fā)現(xiàn)這個名字的類,發(fā)現(xiàn)該類已被加載,并不會重新加載網(wǎng)絡(luò)傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改。可能你會想,如果我們在classpath路徑下自定義一個名為java.lang.SingleInterge類(該類是胡編的)呢?該類并不存在java.lang中,經(jīng)過雙親委托模式,傳遞到啟動類加載器中,由于父類加載器路徑下并沒有該類,所以不會加載,將反向委托給子類加載器加載,最終會通過系統(tǒng)類加載器加載該類。但是這樣做是不允許,因為java.lang是核心API包,需要訪問權(quán)限,強制加載將會報出如下異常

java.lang.SecurityException: Prohibited package name: java.lang

類加載器間的關(guān)系

我們進一步了解類加載器間的關(guān)系(并非指繼承關(guān)系),主要可以分為以下4點

  • 啟動類加載器,由C++實現(xiàn),沒有父類。

  • 拓展類加載器(ExtClassLoader),由Java語言實現(xiàn),父類加載器為null

  • 系統(tǒng)類加載器(AppClassLoader),由Java語言實現(xiàn),父類加載器為ExtClassLoader

  • 自定義類加載器,父類加載器肯定為AppClassLoader。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

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

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