jvm類加載原理和類加載器介紹


虛擬機的類加載機制

  在Class文件中描述的各種信息最終都需要加載到虛擬機中之后才能運行和使用。

    虛擬機把描述類的數據從Class文件加載到內存,並對數據進行校驗,轉換解析和初始化,最終形成可以被虛擬機直接使用的java類型,這就是虛擬機的類加載機制。

    在java語言中,類型的加載、連接和初始化過程都是在程序運行期間完成的,這種策略雖然慧琳兩個類加載時騷味增加一些性能開銷,但是會為java應用程序提供高度的靈活性,java麗天生可以動態擴展的語言特性就是依賴運行期動態加載和動態連接這個特點實現的。

類加載時機

  類從被加載到虛擬機內存中開始,到卸載出內存為止,它的整個生命周期包括:加載、驗證、准備、解析、初始化、使用、卸載7個階段。其中驗證、准備、解析三個部分統稱為連接。

  在這七個階段中加載、驗證、准備、初始化和卸載這5個階段的順序是確定的,類的加載過程必須按着整個順序按部就班的開始,而解析階段則不一定,有時候會在初始化之后進行,這是為了支持劇啊語言的運行時綁定(也成為動態綁定或晚期綁定)。但是要注意的是,這里我們說的是按部就班的“開始”,並沒有說是按部就班的“完成”,也就是說並不是一個階段完成之后再進行下一個階段,而是可能一個階段正在進行中的時候下一個階段就開始了,但是每個階段的“開始時間”是遵循上面的順序的。

  那么什么時候開始類加載過程的第一個階段:加載呢?Java虛擬機規范中並沒有進行強制約束,這點可以交給虛擬機的具體實現來自有把握。但是對初始化階段,虛擬機規范則是嚴格規定了有且只有5種情況必須立即對類進行“初始化”(而加載、驗證、准備自然需要在此之前開始):

  1. 遇到new、getstatic、putstatic或invokestatic這4條字節碼指令時,如果類沒有進行過初始化,則需要先觸發其初始化。
  2. 使用java.lang.reflect包中的方法對類就行反射調用的時候,如果此類沒有進行過初始化,則需要首先對此類進行初始化
  3. 在初始化一個類的時候,如果這個類的父類沒有初始化,則需要先觸發其父類的初始化
  4. 當虛擬機啟動時,用戶需要指定一個要執行的主類(包含main()方法的那個類),虛擬機會先初始化整個主類。
  5. 當使用JDk1.7的動態語言支持時,如果一個java.lang.invoke.MethodHandle實例最后的解析結果REF_getstatic.REF_putStatic、REF_invokeStatic的方法句柄,並且這個方法句柄所對應的類沒有進行過初始化,則需要先觸發其初始化。

  這五種場景種的行為稱為對一個類進行主動引用。除此之外,所有引用類的方式都不會觸發初始化,稱為被動引用。

類加載過程

  Java虛擬機中類加載的全過程,也就是加載、驗證、准備、解析和初始化這五個階段所具體執行的動作。

  • 加載

  加載階段虛擬機需要完成以下三種事情:

  1.通過一個類的全限定名來獲取定義此類的二進制字節流

  2.將這個字節流所代表的靜態存儲結構轉化為方法區的運行時數據結構

  3.在內存中生成一個代表這個類的java.lang.Class對象,作為方法區這個類的各種數據的訪問入口

  相對於類加載過程的其他階段,一個非數組類額加載階段(准確的說,時加載階段中獲取類的二進制字節流的動作)是開發人員可控性最強的,因為加載階段即可以使用胸痛提供的引導類加載器完成,也可以由用戶自定義的類加載器去完成,開發人員可以通過定義自己的類加載器去控制字節流的獲取方式。

  對於數組類而言,情況就有所不同,數組類本身就不通過類加載器創建,他是由java虛擬機直接拆功能鍵的。但是數組類和類加載器仍然由很迷奇的關系,因為數組類的元素類型最終是要靠類加載器去創建。

  • 驗證

  驗證是連接階段的第一步。這個階段的目的是為了確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機自身的安全。

  驗證階段大致會完成下面是個階段的驗證動作:

  1.文件格式校驗

 

  2.元數據校驗

 

  3.字節碼校驗

  

  4.符號引用校驗

 

  • 准備

  准備階段是正式為類變量分配內存並設置類變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。這個階段有兩個容易產生混淆的概念需要強調一下,首先,這時候進行內存分配的勁爆扣類變量(被static修飾的變量),而不包括實例變量,實例變量將會在對象實例化的時候隨着兌現夠義氣分配在java堆中。其次,這里所說的初始值“通常情況”下是數據類型的零值,例如:public static int a=123;那么此時a在准備階段過后的初始值為0,而不是123,因為這個時候尚未開始執行任何java方法,而把a賦值為123的操作是在初始化階段進行的。注意,剛才說的是“通常情況”下初始值為零值,那相對的會有一些“特殊情況”:如果類字段的字段屬性表中存在ConstantValue屬性,拿在准備階段變量a就會被初始化為ConstantValue屬性所指定的值,假設上面類變量a的定義變為:public static final int a=123;編譯時javac將會為a生成ConstantValue屬性,在准備階段虛擬機就會根據ConstantValue的設置將a賦值為123.

  • 解析

  解析階段是虛擬機將常量池內的符號引用代替為直接引用的過程。

  符號引用:符號引用以一組符號來描述所引用的目標,符號可以是任何形式的字面量,只要使用時能無歧義的定位到目標即可。符號引用與虛擬機實現的內存布局無關,引用的目標並不一定已經加載到內存中。

  直接引用:直接引用可以是直接指向目標的指針、相對偏移量或是一個能簡介定位到的目標的句柄。直接引用是和虛擬機實現的內存布局有關的。如果有了直接引用,那引用的目標必定已經在內存中存在了。

  • 初始化

  類初始化階段是類加載過程的最后一步,前面的類加載過程中,除了在加載階段用戶應用程序可以通過自定義類加載器參與之外,其余動作完全由虛擬機主導和控制。到了初始化階段,才真正的開始執行類中定義的java程序代碼了(或者說是字節碼)。

類和類加載器

  虛擬機設計團隊把類加載階段中的“通過一個類的全限定名來獲取描述此來的二進制字節流”這個動作放到了Java迅即外部去實現,一邊讓應用程序自己決定如何去獲取所需要的類。實現這個動作的代碼模塊稱為“類加載器”。

  類加載器雖然只是用於實現類的加載動作,但是它在java程序中起到的作用卻遠遠不限與類加載階段。對於任意一個類,都需要由加載它的類加載器和這個類本身一同確立其在Java虛擬機中的唯一性,每一個類加載器,都擁有一個獨立的類名稱空間。

類加載器的分類:

  從java虛擬機角度來講,只存在兩種不同的類加載器:一種是啟動類加載器,這個類加載器使用C++語言實現,是虛擬機自身的一部分;另一種就是所有類的類加載器,這些類加載器都有java語言實現,獨立與虛擬機外部,並且都繼承自抽象類java.lang.ClassLoader.

  但是從Java開發人員角度來看,類加載器還可以划分的更細致一些,大致可以分為:

  1.啟動類加載器:這個類加載器負責將存放在<JAVA_HOME>\lib目錄中的,或者被-XbootClasspath參數所指定的路徑中的,並且是虛擬機識別的類庫加載到虛擬機內存中。

  2.擴展類加載器
  3.應用程序類加載器

  4.自定義類加載器

  此四種類型的加載器從上到下存在父子關系,但是這里累加子啊之間額父子關系額一般不會以繼承的關系來實現,而是都使用組合關系來服用父加載器的代碼。

雙親委派模型

 

破壞雙親委派模型

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM