1. 程式人生 > >(轉) JVM——Java類加載機制總結

(轉) JVM——Java類加載機制總結

自定義類加載器 參數 AC str http color sdn mda pan

背景:對java類的加載機制,一直都是模糊的理解,這篇文章看下來清晰易懂。

轉載:http://blog.csdn.net/seu_calvin/article/details/52301541

1. 類加載器的組織結構

類加載器 ClassLoader是具有層次結構的,也就是父子關系。其中,Bootstrap是所有類加載器的父親。

(1)Bootstrapclass loader: 啟動類加載器

當運行Java虛擬機時,這個類加載器被創建,它負責加載虛擬機的核心類庫,如java.lang.*等。

2)Extensionclass loader:標準擴展類加載器

用於加載除了基本 API之外的一些拓展類。

(3)AppClassLoader:加載應用程序和程序員自定義的類。

運行下面的程序,結果也顯示出來了:

技術分享圖片

從運行結果可以看出加載器之間的父子關系,ExtClassLoader的父Loader返回了null

原因是BootstrapLoader(啟動類加載器)是用C語言實現的,找不到一個確定的返回父Loader的方式。

2. 類的加載機制

類被加載到虛擬機內存包括加載、鏈接、初始化幾個階段。其中鏈接又細化分為驗證、準備、解析

這裏需要註意的是,解析階段在某些情況下可以在初始化階段之後再開始,這是為了支持Java的運行時綁定。各個階段的作用整理如下:

2.1 加載階段

加載階段可以使用系統提供的類加載器(ClassLoader)來完成,也可以由用戶自定義的類加載器完成,開發人員可以通過定義類加載器去控制字節流的獲取方式。

ps:可以自定義類加載器,從加密和安全兩個角度出發來加載特殊的類。具體參考——

(1)通過類的全名產生對應類的二進制數據流

(2)將這些二進制數據流轉換為方法區的運行時數據結構

(3)創建代表這個類的java.lang.Class對象。作為方法區這些數據的訪問入口

2.2 鏈接階段(實現 Java 的動態性的重要一步)

(1)驗證:驗證階段的主要目的是確保class文件字節流的正確性,要驗證比如class文件格式規範、這個類是否繼承了final類、不能把一個父類對象賦值給子類數據類型等等。

(2)準備:準備階段為方法區中的靜態變量分配內存空間。並將其賦值為初始值,所有原始類型的值都為0。如float為0f、 int為0、boolean為0、引用類型為null。

(3)解析解析階段把符號引用解析為直接引用。

符號引用是一個字符串,它唯一標識一個類、一個字段、一個方法等目標。

直接引用對於類變量、類方法指的是指向方法區的指針,然後對於實例方法、實例對象來說就是偏移量,比如一個實例方法,子類中方法表中的偏移量和父類是一致的,這個偏移量可以確定某個方法的位置。

2.3 初始化

到了初始化階段,才是真正執行用戶定義的程序代碼。在初始化階段就是執行類構造器方法的過程,工作包括賦值類變量、靜態語句塊的合並。

//定義在靜態語句塊之後的變量可以賦值,但不能訪問  
public class Test{  
    static{  
        i=0;//給變量賦值,可以通過編譯  
        System.out.print(i);//這句編譯器會提示非法向前引用  
    }  
    static int i=1;  
}  

初始化過程會被觸發的條件匯總

(1)使用new關鍵字實例化對象、訪問一個類的靜態字段、靜態方法的時候。

(2)對類進行反射調用的時候。

(3)當初始化子類時,如果發現其父類還沒有進行過初始化,則進行父類的初始化

【關於構造器方法拓展知識】(可以不看)

(1)類構造器<clinit>()方法與類的構造函數不同,它不需要顯式調用父類構造,虛擬機會保證在子類<clinit>()方法執行之前,父類的<clinit>()方法已經執行完畢。因此在虛擬機中的第一個執行的<clinit>()方法的類肯定是java.lang.Object。

(2)由於父類的<clinit>()方法先執行,也就意味著父類中定義的靜態語句塊要優先於子類的變量賦值操作

(3)<clinit>()方法不是必須的,如果一個類中沒有靜態語句,那麽編譯器可以不為這個類生成<clinit>()方法。

(4)接口中不能使用靜態語句塊,和類不同的是,執行接口的<clinit>()方法不需要先執行父接口的<clinit>()方法。只有當父接口中定義的變量被使用時,父接口才會被初始化。另外,接口的實現類在初始化時也一樣不會執行接口的<clinit>()方法。

(5)虛擬機會保證一個類的<clinit>()方法在多線程環境中被正確加鎖和同步,可能會導致阻塞。

3. 類加載的三種方式

(1)由 new 關鍵字創建一個類的實例。

(2)調用 Class.forName() 方法,通過反射加載類。

(3)調用某個ClassLoader實例的loadClass()方法。

三者的區別匯總如下:

(1)方法1和2都是使用的當前類加載器。方法3是由用戶指定的類加載器加載

(2)方法1是靜態加載,2、3是動態加載。

(3)對於兩種動態加載,如果程序需要類被初始化,就必須使用Class.forName(name)的方式。

Class.forName(className);  
//實際上是調用的是:  
Class.forName(className, true, this.getClass().getClassLoader());//第二個參為true即默認類需要初始化,初始化會觸發目標對象靜態塊的執行和靜態變量的初始化  

ClassLoader.loadClass(className);  
//實際上調用的是:  
ClassLoader.loadClass(name, false);//第二個參數即默認得到的class還沒有進行鏈接,意味著不進行初始化等系列操作,即靜態代碼塊不會執行 

(轉) JVM——Java類加載機制總結