JVM類加載器
1.類加載子系統的作用

類加載器子系統負責從檔案系統或者網路中加載class檔案,class檔案在檔案開頭有特定的檔案標識,
2.類加載程序
當程式主動使用某個類時,如果該類還未被加載到記憶體中,則JVM會通過加載、連接、初始化3個步驟來對該類進行初始化,如果沒有意外,JVM將會連續完成3個步驟,所以有時也把這3步驟統稱為類加載或類初始化,
類被加載到 JVM 開始,到卸載出記憶體,整個生命周期如圖:

1.加載
- 通過類名(地址)獲取此類的二進制位元組流,
- 將這個位元組流所代表的靜態存盤結構轉換為方法區(元空間)的運行時結構,
- 在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類的各種資料訪問入口,
2.鏈接
就是將已經讀入記憶體的類的二進制資料合并到JVM運行時環境中去,包含以下步驟:
-
驗證
檢驗被加載的類是否有正確的內部結構,并和其他類協調一致,
-
準備
準備階段則負責為類的靜態屬性分配記憶體,并設定默認初始值;不包含final修飾的static實體變數,在編譯時進行初始化,不會為實體變數初始化,
-
決議
將類的二進制資料中的符號參考替換成直接參考,
3.初始化
類什么時候初始化?
- 創建類的實體,new物件
- 訪問某個類或介面的靜態變數,或者對該靜態變數賦值
- 呼叫類的靜態方法
- 反射(Class.forName(" "))
- 初始化一個類的子類(首先會初始化子類的父類)
- JVM啟動時標明的啟動類,即檔案名和類名相同的那個類
注意:對于一個final型別的靜態變數,如果該變數的值在編譯時就可以確定下來,那么這個變數相當于“宏變數”,Java編譯器會在編譯時直接把這個變數出現的地方替換成它的值,因此即使程式使用該靜態變數,也不會導致該類的初始化,反之,如果final型別的靜態Field的值不能在編譯時確定下來,則必須等到運行時才可以確定該變數的值,如果通過該類來訪問它的靜態變數,則會導致該類被初始化,
類的初始化順序
對static修飾的變數或陳述句塊進行賦值,
如果同時包含多個靜態變數和靜態代碼塊,則按照自上而下的順序依次執行,
如果初始化一個類的時候,其父類尚未初始化,則優先初始化其父類,
順序是:父類static -> 子類static -> 父類構造方法 -> 子類構造方法
3.類加載器分類
JVM支持兩種型別的類加載器,分別為引導類加載器(Bootstrap ClassLoader)和自定義類加載器(User-Defined ClassLoader)
無論類加載器的型別如何劃分,在程式中我們最常見的類加載器始終只有3個:

-
自定義類加載器(User-Defined ClassLoader)
從概念上來講,自定義類加載器一般指的是程式匯總有開發人員自定義的一類加載器,但是Java虛擬機規范卻沒有這么定義,而是將所有派生于抽象類ClassLoader的類加載器都劃分為自定義類加載器,
-
擴展類加載器(Extension ClassLoader)
Java語言撰寫的,由sun.misc.Launcher$ExtClassLoader實作,父類加載器為null,
派生于ClassLoader類,
上層類加載器為引導類加載器,
它負責加載JRE的擴展目錄,
從java.ext.dirs系統屬性所指定的目錄中加載類別庫,或從JDK系統安裝目錄的jre/lib/ext子目錄(擴展目錄)下加載類別庫,如果用戶創建的jar放在此目錄下,也會自動由擴展類加載器加載,
-
應用程式類加載器(系統類加載器 Application ClassLoader)
Java語言撰寫的,由sun.misc.Launcher$AppClassLoader實作,父類加載器為ExtClassLoader,
派生于ClassLoader類,
上層類加載器為擴展類加載器,
加載我們自己定義的類,
該類加載器是程式中默認的類加載器,
通過類名.class.getClassLoader(),ClassLoader.getSystemClassLoader()來獲得,
-
-
引導類加載器(啟動類加載器/根類加載器)(Bootstrap ClassLoader)
這個類加載器使用C/C++語言實作,嵌套在JVM內部,用來加載Java核心類別庫,
并不繼承于java.lang.ClassLoader沒有父加載器,
負責加載擴展類加載器和應用類加載器,并為它們指定父類加載器,
出于安全考慮,參考類加載器只加載器包名為java,javax,sun等開頭的類,
注意:ClassLoader類,它是一個抽象類,其后所有類加載器都繼承自ClassLoader(不包括啟動類加載器)
類加載器加載Class大致要經過如下8個步驟:
- 檢測此Class是否載入過,即在緩沖區中是否有此Class,如果有直接進入第8步,否則進入第2步,
- 如果沒有父類加載器,則要么Parent是根類加載器,要么本身就是根類加載器,則跳到第4步,如果父類加載器存在,則進入第3步,
- 請求使用父類加載器去載入目標類,如果載入成功則跳至第8步,否則接著執行第5步,
- 請求使用根類加載器去載入目標類,如果載入成功則跳至第8步,否則跳至第7步,
- 當前類加載器嘗試尋找Class檔案,如果找到則執行第6步,如果找不到則執行第7步,
- 從檔案中載入Class,成功后跳至第8步,
- 拋出ClassNotFountException例外,
- 回傳對應的java.lang.Class物件,
4.類加載機制JVM的類加載機制主要有3種
JVM的類加載機制主要有3種
- 全盤負責:所謂全盤負責,就是當一個類加載器負責加載某個Class時,該Class所依賴和參考其他Class也將由該類加載器負責載入,除非顯示使用另一個類加載器來載入,
- 雙親委派:所謂的雙親委派,則是先讓父類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類,通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委托給父加載器,依次遞回,如果父加載器可以完成類加載任務,就成功回傳;只有父加載器無法完成此加載任務時,才自己去加載,
- 快取機制:快取機制將會保證所有加載過的Class都會被快取,當程式中需要使用某個Class時,類加載器先從快取區中搜尋該Class,只有當快取區中不存在該Class物件時,系統才會讀取該類對應的二進制資料,并將其轉換成Class物件,存入緩沖區中,這就是為什么修改了Class后,必須重新啟動JVM,程式所做的修改才會生效的原因,
細講一下雙親委派機制(面試):
作業原理:
如果一個類加載器收到了類加載請求,它并不會自己先去加載,而是把這個請求委托給父類的加載器區執行,如果父類加載器還存在其父類加載器,則進一步向上委托,依次遞回,請求最終將到達頂層的啟動類加載器,如果父類加載器可以完成類加載任務,就成功回傳,倘若父加載器無法完成此加載任務,子加載器才會嘗試自己去加載,如果均加載失敗,就會拋出ClassNotFoundException例外,這就是雙親委派模式,即每個兒子都很懶,每次有活就丟給父親去干,直到父親說這件事我也干不了了時,兒子自己才想辦法去完成,

雙親委派優點:
- 安全,可避免用戶自己撰寫的類動態替換Java的核心類,如java.lang.String,,java核心api中定義型別不會被隨意替換,假設通過網路傳遞一個名為java.lang.Integer的類,通過雙親委托模式傳遞到啟動類加載器,而啟動類加載器在核心Java API發現這個名字的類,發現該類已被加載,并不會重新加載網路傳遞的過來的java.lang.Integer,而直接回傳已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改,
- 避免全限定命名的類重復加載(使用了findLoadClass()判斷當前類是否已加載),Java類隨著它的類加載器一起具備了一種帶有優先級的層次關系,通過這種層級關可以避免類的重復加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次,5
5.沙箱安全機制
作用:防止惡意代碼污染java源代碼,
比如我們定義了一個類名為String所在包也命名為java.lang,因為這個類本來屬于jdk的,如果沒有沙箱安全機制,這個類將會污染到系統中的String,但是由于沙箱安全機制,所以就委托頂層的引導類加載器查找這個類,如果沒有的話就委托給擴展類加載器,再沒有就委托到系統類加載器,但是由于String就是jdk源代碼,所以在引導類加載器那里就加載到了,先找到先使用,所以就使用引導類加載器里面的String,后面的一概不能使用,這就保證了不被惡意代碼污染,
6.類的主動使用/被動使用
JVM規定,每個類或者介面被首次主動使用時才對其進行初始化,有主動使用,自然就有被動使用,
主動使用:
- 通過new關鍵字被導致類的初始化,導致類的加載并初始化,
- 訪問類或介面的靜態變數,包括讀取和更新,或者對該靜態變數賦值,
- 訪問類的靜態方法,
- 對某個類進行反射操作,會導致類的初始化,
- 初始化子類會導致父類的初始化,
- 執行該類的main函式,
- Java虛擬機啟動時被表明為啟動類的類(JavaTest)
被動使用:
除了上面的幾種主動使用其余就是被動使用了,
-
參考該類的靜態常量,不會導致初始化,但是也有意外,這里的常量是指已經指定字面量的常量,對于那些需要一些計算才能得出結果的常量就會導致初始化,
public final static int NUMBER = 5 ; //不會導致類初始化,被動使用 public final static int RANDOM = new Random().nextInt() ;//會導致類的初 始化,主動使用 -
構造某個類的陣列時不會導致該類的初始化,
Student[] students = new Student[10] ;
注意:主動使用和被動使用的區別在于類是否會被初始化,
7.類裝載方式
面試題:
描述一下JVM加載Class檔案的原理機制
java中的所有類,都需要由類加載器裝載到JVM中才能運行,類加載器本身也是一個類,而它的作業就是把class檔案從硬碟讀取到記憶體中,在寫程式的時候,我們幾乎不需要關心類的加載,因為這些都是隱式裝載的,除非我們有特殊的用法,像是反射,就需要顯示的加載所需要的類,
類裝載方式:
- 隱式裝載,程式在運行程序中當碰到通過new等方式生成物件時,隱式呼叫類裝載器加載對應的類到jvm中,
- 顯式裝載,通過class.forName()等方法,顯式加載需要的類,
java類的加載是動態的,它并不會一次性將所有類全部加載后再運行,而是保證程式運行的基礎類完全加載到JVM中,至于其他類,則在需要的時候才加載,節省記憶體開銷,
面試題:
在jvm中如何判斷兩個物件是屬于同一個類?
1.類的全類名(地址)完全一致,
2.類的加載器必須相同,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/274716.html
標籤:java
上一篇:JVM面經
