Java-類加載機制-學習總結

類加載機制

原理 (類的加載過程及其最終產品):

JVM將class文件字節碼文件加載到內存中, 並將這些靜態數據轉換成方法區中的運行時數據結構,在堆(並不一定在堆中,HotSpot在方法區中)中生成一個代表這個類的java.lang.Class 對象,作爲方法區類數據的訪問入口。

過程(類的生命週期):

JVM類加載機制分爲五個部分:加載,驗證,準備,解析,初始化,下面我們就分別來看一下這五個過程。其中加載、檢驗、準備、初始化和卸載這個五個階段的順序是固定的,而解析則未必。爲了支持動態綁定,解析這個過程可以發生在初始化階段之後。
在這裏插入圖片描述

加載:

加載過程主要完成三件事情:

通過類的全限定名來獲取定義此類的二進制字節流
將這個類字節流代表的靜態存儲結構轉爲方法區的運行時數據結構
在堆中生成一個代表此類的java.lang.Class對象,作爲訪問方法區這些數據結構的入口。
這個過程主要就是類加載器完成。

校驗:

此階段主要確保Class文件的字節流中包含的信息符合當前虛擬機的要求,並且不會危害虛擬機的自身安全。

文件格式驗證:基於字節流驗證。
元數據驗證:基於方法區的存儲結構驗證。
字節碼驗證:基於方法區的存儲結構驗證。
符號引用驗證:基於方法區的存儲結構驗證。

文件格式驗證:主要驗證字節流是否符合Class文件格式規範,並且能被當前的虛擬機加載處理。例如:主,次版本號是否在當前虛擬機處理的範圍之內。常量池中是否有不被支持的常量類型。指向常量的中的索引值是否存在不存在的常量或不符合類型的常量。
元數據驗證:對字節碼描述的信息進行語義的分析,分析是否符合java的語言語法的規範。
字節碼驗證:最重要的驗證環節,分析數據流和控制,確定語義是合法的,符合邏輯的。主要的針對元數據驗證後對方法體的驗證。保證類方法在運行時不會有危害出現。
符號引用驗證:主要是針對符號引用轉換爲直接引用的時候,是會延伸到第三解析階段,主要去確定訪問類型等涉及到引用的情況,主要是要保證引用一定會被訪問到,不會出現類等無法訪問的問題。

準備:

爲類變量分配內存,並將其初始化爲默認值。(此時爲默認值,在初始化的時候纔會給變量賦值)即在方法區中分配這些變量所使用的內存空間。例如:

public static int value = 123;
此時在準備階段過後的初始值爲0而不是123;將value賦值爲123的putstatic指令是程序被編譯後,存放於類構造器方法之中.特例:

public static final int value = 123;
此時value的值在準備階段過後就是123。

解析:

把類型中的符號引用轉換爲直接引用。
符號引用與虛擬機實現的佈局無關,引用的目標並不一定要已經加載到內存中。各種虛擬機實現的內存佈局可以各不相同,但是它們能接受的符號引用必須是一致的,因爲符號引用的字面量形式明確定義在Java虛擬機規範的Class文件格式中。
直接引用可以是指向目標的指針,相對偏移量或是一個能間接定位到目標的句柄。如果有了直接引用,那引用的目標必定已經在內存中存在

主要有以下四種:
類或接口的解析
字段解析
類方法解析
接口方法解析

初始化:

初始化階段是執行類構造器方法的過程。方法是由編譯器自動收集類中的類變量的賦值操作和靜態語句塊中的語句合併而成的。虛擬機會保證方法執行之前,父類的方法已經執行完畢。如果一個類中沒有對靜態變量賦值也沒有靜態語句塊,那麼編譯器可以不爲這個類生成()方法。
java中,對於初始化階段,有且只有以下五種情況纔會對要求類立刻“初始化”(加載,驗證,準備,自然需要在此之前開始):
使用new關鍵字實例化對象、訪問或者設置一個類的靜態字段(被final修飾、編譯器優化時已經放入常量池的例外)、調用類方法,都會初始化該靜態字段或者靜態方法所在的類。
初始化類的時候,如果其父類沒有被初始化過,則要先觸發其父類初始化。
使用java.lang.reflect包的方法進行反射調用的時候,如果類沒有被初始化,則要先初始化。
虛擬機啓動時,用戶會先初始化要執行的主類(含有main)
jdk 1.7後,如果java.lang.invoke.MethodHandle的實例最後對應的解析結果是 REF_getStatic、REF_putStatic、REF_invokeStatic方法句柄,並且這個方法所在類沒有初始化,則先初始化。

類加載器

類加載器負責加載所有的類,其爲所有被載入內存中的類生成一個java.lang.Class實例對象。一旦一個類被加載如JVM中,同一個類就不會被再次載入了。正如一個對象有一個唯一的標識一樣,一個載入JVM的類也有一個唯一的標識。在Java中,一個類用其全限定類名(包括包名和類名)作爲標識;但在JVM中,一個類用其全限定類名和其類加載器作爲其唯一標識。例如,如果在pg的包中有一個名爲Person的類,被類加載器ClassLoader的實例kl負責加載,則該Person類對應的Class對象在JVM中表示爲(Person.pg.kl)。這意味着兩個類加載器加載的同名類:(Person.pg.kl)和(Person.pg.kl2)是不同的、它們所加載的類也是完全不同、互不兼容的。
JVM預定義有三種類加載器,當一個 JVM啓動的時候,Java開始使用如下三種類加載器:
1)根類加載器(bootstrap class loader):它用來加載 Java 的核心類,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader(負責加載$JAVA_HOME中jre/lib/rt.jar裏所有的class,由C++實現,不是ClassLoader子類)。由於引導類加載器涉及到虛擬機本地實現細節,開發者無法直接獲取到啓動類加載器的引用,所以不允許直接通過引用進行操作。

下面程序可以獲得根類加載器所加載的核心類庫,並會看到本機安裝的Java環境變量指定的jdk中提供的核心jar包路徑:

public class ClassLoaderTest {
	public static void main(String[] args) {
		URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs();
		for(URL url : urls){
			System.out.println(url.toExternalForm());
		}
	}
}

運行結果:
在這裏插入圖片描述
2)擴展類加載器(extensions class loader):它負責加載JRE的擴展目錄,lib/ext或者由java.ext.dirs系統屬性指定的目錄中的JAR包的類。由Java語言實現,父類加載器爲null。

3)系統類加載器(system class loader):被稱爲系統(也稱爲應用)類加載器,它負責在JVM啓動時加載來自Java命令的-classpath選項、java.class.path系統屬性,或者CLASSPATH換將變量所指定的JAR包和類路徑。程序可以通過ClassLoader的靜態方法getSystemClassLoader()來獲取系統類加載器。如果沒有特別指定,則用戶自定義的類加載器都以此類加載器作爲父加載器。由Java語言實現,父類加載器爲ExtClassLoader。
類加載器加載Class大致要經過如下8個步驟:
1.檢測此Class是否載入過,即在緩衝區中是否有此Class,如果有直接進入第8步,否則進入第2步。
2.如果沒有父類加載器,則要麼Parent是根類加載器,要麼本身就是根類加載器,則跳到第4步,如果父類加載器存在,則進入第3步。
3.請求使用父類加載器去載入目標類,如果載入成功則跳至第8步,否則接着執行第5步。
4.請求使用根類加載器去載入目標類,如果載入成功則跳至第8步,否則跳至第7步。
5.當前類加載器嘗試尋找Class文件,如果找到則執行第6步,如果找不到則執行第7步。
6.從文件中載入Class,成功後跳至第8步。
7.拋出ClassNotFountException異常。
8.返回對應的java.lang.Class對象。

類加載機制

JVM的類加載機制主要有如下3種。

全盤負責:所謂全盤負責,就是當一個類加載器負責加載某個Class時,該Class所依賴和引用其他Class也將由該類加載器負責載入,除非顯示使用另外一個類加載器來載入。

雙親委派:所謂的雙親委派,則是先讓父類加載器試圖加載該Class,只有在父類加載器無法加載該類時才嘗試從自己的類路徑中加載該類。通俗的講,就是某個特定的類加載器在接到加載類的請求時,首先將加載任務委託給父加載器,依次遞歸,如果父加載器可以完成類加載任務,就成功返回;只有父加載器無法完成此加載任務時,才自己去加載。

緩存機制。緩存機制將會保證所有加載過的Class都會被緩存,當程序中需要使用某個Class時,類加載器先從緩存區中搜尋該Class,只有當緩存區中不存在該Class對象時,系統纔會讀取該類對應的二進制數據,並將其轉換成Class對象,存入緩衝區中。這就是爲很麼修改了Class後,必須重新啓動JVM,程序所做的修改纔會生效的原因。

這裏說明一下雙親委派機制:
雙親委派機制,其工作原理的是,如果一個類加載器收到了類加載請求,它並不會自己先去加載,而是把這個請求委託給父類的加載器去執行,如果父類加載器還存在其父類加載器,則進一步向上委託,依次遞歸,請求最終將到達頂層的啓動類加載器,如果父類加載器可以完成類加載任務,就成功返回,倘若父類加載器無法完成此加載任務,子加載器纔會嘗試自己去加載,這就是雙親委派模式,即每個兒子都很懶,每次有活就丟給父親去幹,直到父親說這件事我也幹不了時,兒子自己纔想辦法去完成。
雙親委派機制的優勢:採用雙親委派模式的是好處是Java類隨着它的類加載器一起具備了一種帶有優先級的層次關係,通過這種層級關可以避免類的重複加載,當父親已經加載了該類時,就沒有必要子ClassLoader再加載一次。其次是考慮到安全因素,java核心api中定義類型不會被隨意替換,假設通過網絡傳遞一個名爲java.lang.Integer的類,通過雙親委託模式傳遞到啓動類加載器,而啓動類加載器在覈心Java API發現這個名字的類,發現該類已被加載,並不會重新加載網絡傳遞的過來的java.lang.Integer,而直接返回已加載過的Integer.class,這樣便可以防止核心API庫被隨意篡改。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章