(轉) JVM——Java類加載機制總結
背景:對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類加載機制總結