1. 程式人生 > 實用技巧 >Class檔案載入過程

Class檔案載入過程

1、類載入器以及雙親委派機制

  

比如custom ClassLoader要載入一個類,會先詢問它的parent類,然後AppClassLoader會看它有沒有載入過這個類,如果沒有繼續詢問它的parent即ExtensionClassLoader

直到詢問至BootstrapClassLoader也沒有載入,因為BootstrapClassLoader只負責載入核心類,就命令它的child即ExtensionClassLoader載入,但是這個類也不在它的載入

範圍之內,就再向下傳達命令。最後就到了customlClassLoader這個載入器載入。

類載入器載入原始碼:

  

 1
protected Class<?> loadClass(String name, boolean resolve) 2 throws ClassNotFoundException 3 { 4 synchronized (getClassLoadingLock(name)) { 5 // First, check if the class has already been loaded 6 Class<?> c = findLoadedClass(name); 7 if
(c == null) { 8 long t0 = System.nanoTime(); 9 try { 10 if (parent != null) { 11 c = parent.loadClass(name, false); 12 } else { 13 c = findBootstrapClassOrNull(name); 14 }
15 } catch (ClassNotFoundException e) { 16 // ClassNotFoundException thrown if class not found 17 // from the non-null parent class loader 18 } 19 20 if (c == null) { 21 // If still not found, then invoke findClass in order 22 // to find the class. 23 long t1 = System.nanoTime(); 24 c = findClass(name); 25 26 // this is the defining class loader; record the stats 27 sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0); 28 sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1); 29 sun.misc.PerfCounter.getFindClasses().increment(); 30 } 31 } 32 if (resolve) { 33 resolveClass(c); 34 } 35 return c; 36 } 37 }

由程式碼可以看出,載入類的時候會先自己找,沒找到就找它的父載入器,父載入器也是如此,就類似於遞迴操作。如果沒有找到合適的載入器就呼叫 findClass 方法。

1    protected Class<?> findClass(String name) throws ClassNotFoundException {
2         throw new ClassNotFoundException(name);
3     }

進入findClass就直接丟擲了ClassNotFoundException。像AppClassLoader等繼承與URLClassLoader,而URLClassLoader重寫了此方法。

所以如果想自定義類載入器,就只要繼承ClassLoader然後重寫這個方法就行。

2、類載入過程

這裡的載入過程是嚴格按照載入開始順序進行的,注意是載入開始而不是載入完成。也就是有可能會有兩個或幾個階段是同時進行的。

比如下面提到的驗證過程中的符號引用驗證是在解析階段開始之後進行。

載入(loading):

    (1)、通過一個類的全限定名來獲取定義此類的二進位制位元組流。

    (2)、將這個位元組流所代表的靜態儲存結構轉化為方法去執行時資料結構。

    (3)、在記憶體中生成一個代表這個類的java.lang.Class物件,作為方法區這個類各種資料的訪問入口

連線(linking):

  1、驗證(verification):

    (1)、檔案格式驗證。是否以魔術0xCAFEBABE開頭、檢查常量池tag標誌等。

    (2)、元資料驗證。對位元組碼描述的資訊進行語義分析。例如驗證這個類是否有父類、在這個類的父類是否繼承了不允許被繼承的類(被final修飾的類)。

    (3)、位元組碼驗證:主要確定程式語義是否合法,符合邏輯的。再元資料驗證後就要對類的方法體進行驗證。比如保證方法體中的型別轉化總是有效的。

    (4)、符號引用驗證:這個發生連線的第三階段解析階段將符號引用轉化為直接引用的時候。驗證該類是否缺少或者被禁止訪問它依賴的某些外部類、方法、欄位等資源。

  2、準備(preparation):

    準備階段是正式為類中定義的變數(即靜態變數)分配記憶體並設定類變數初始值的階段。jdk8智鬥,類變數會隨著Class物件一起存放在Java堆中。這裡進行記憶體分配的僅包括

    類變數,而不包括例項變數,例項變數將會在物件例項化時一起分配在Java堆中。並且這裡所說的初始值一般都是這個資料型別的零值。但是如果是static final定義變數就會設定為

    它所指定的值。

  3、解析(resolution):

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

初始化(initializing)

    在準備階段變數已經賦過一次初始零值,而在初始化階段則會根據程式設計師程式編碼制定的主觀計劃去初始化類變數和其他資源