(一)類載入機制與反射
Java類載入器除了根類載入器之外,其他類載入器都是使用Java語言編寫的,所以程式員完全可以開發自己的類載入器,通過使用自訂類載入器,可以完成一些特定的功能。
(二)類的載入、串連和初始化
系統可能在第一次使用某個類時載入該類,也可能採用預先載入機制來載入某個類。
1. JVM和類
當我們調用Java命令運行某個Java程式時,該命令將會啟動一個Java虛擬機器進程,不管該Java程式有多麼複雜,該程式啟動了多少個線程,它們都處於該Java虛擬機器進程裡。
同一個JVM的所有線程、所有變數都處於同一個進程裡,它們都使用該JVM進程的記憶體區。
當系統出現以下幾種情況時,JVM進程將被終止:
(1)程式運行到最後正常結束。
(2)程式運行到使用System.exit()或Runtime.getRuntime().exit()代碼處結束程式。
(3)程式執行過程中遇到未捕獲的異常或錯誤而結束。
(4)程式所在平台強制結束了JVM進行。
下面舉例說明:不同JVM程式的運行情況:
public class A{ //定義該類的靜態Field public static int a = 6;}
public class ATest1{ public static void main(String[] args) { //建立A類的執行個體 A a = new A(); //讓a執行個體的a Field值自增 a.a++; System.out.println(a.a); }}
運行結果:7
public class Atest2{ public static void main(String[] args) { //建立A類執行個體 A b = new A(); //輸出b執行個體的a Field值 System.out.println(b.a); }}
運行結果:6
因為運行ATest1和ATest2是兩次運行JVM進程,第一次運行JVM結束後,它對A類所做的修改將全部丟失---第二次運行JVM時將再次初始化A類。
2. 類的載入
當程式主動使用某個類時,如果該類還未被載入到記憶體中,則系統會通過載入、串連、初始化3個步驟來對該類進行初始化。
類載入指的是將類的class檔案讀入記憶體,並為之建立一個java.lang.Class對象,也就是說,當程式中使用任何類時,系統都會為之建立一個java.lang.Class對象。
類的載入由類載入器完成,類載入器通常由JVM提供,JVM提供的這些類載入器通常被稱為系統類別載入器。除此之外,開發人員還可以通過繼承ClassLoader基類來建立自己的類載入器。
通過使用不同的類載入器,可以從不同來源載入類的位元據:
1.從本地系統載入class檔案。
2.從JAR包載入class檔案
3.通過網路載入class檔案
4.把一個Java源檔案動態編譯,並執行載入
( 類載入器通常無須等到“首次使用”該類時才載入該類,Java虛擬機器規範允許系統積極式載入某些類。)
3. 類的串連
當類被載入之後,系統為之產生一個對應的Class對象,接著將會進入串連階段,串連階段負責把類的位元據合并到JRE中。
類串連又可分為如下3個階段:
(1)驗證:驗證階段用於檢驗被載入的類是否有正確的內部結構,並和其他類協調一致。
(2)準備:類準備階段則負責為類的靜態Field分配記憶體,並設定預設初始值。
(3)解析:將類的位元據中的符號引用替換成直接引用。
4. 類的初始化
在類的初始化階段,虛擬機器負責對類進行初始化,主要就是對靜態Field進行初始化。在Java類中對靜態Field指定初始值有兩種方式:
(1)聲明靜態Field時指定初始值
(2)使用靜態初始化塊為靜態Field指定初始值
public class Test{ //聲明a變數時指定初始值 static int a = 5; static int b; static int c; static { //使用靜態初始化塊b變數指定初始值 b = 6; }}
對於上面代碼,程式為靜態Field a、b都顯示指定了初始值,所以這兩個靜態Field的值分別為5、6,但靜態Field c則沒有指定初始值,它將採用預設初始值0
JVM初始化一個類包含如下幾個步驟:
(1)假如這個類還沒有被載入和串連,則程式先載入並串連該類。
(2)假如該類的直接父類還沒有被初始化,則先初始化其直接父類。
(3)假如類中有初始化語句,則系統依次執行這些初始化語句。
(注意:當執行第2個步驟時,系統對直接父類的初始化步驟也遵循此步驟1-3;如果該直接父類又有直接父類,則系統再次重複這3個步驟來先初始化這個父類....依次類推,所以JVM最先初始化的總是java.lang.Object類。當程式主動使用任何一個類時,系統會保證該類以及所有父類都會被初始化)
(三)類初始化時機
當Java程式首次通過下面6種方式來使用某個類或介面時,系統就會初始化該類或介面:
(1)建立類的執行個體。為某個類建立執行個體的方法包括:使用new操作符來建立執行個體,通過反射來建立執行個體,通過還原序列化的方式來建立執行個體。
(2)調用某個類的靜態方法
(3)訪問某個類或介面的靜態Field,或為該靜態Filed賦值。
(4)使用反射方式來強制建立某個類或介面對應的java.lang.Class對象。
(5)初始化某個類的子類。當初始化某個類的子類時,該子類的所有父類都會初始化。
(6)直接使用java.exe命令來運行某個主類。
對於一個final型的靜態Field,如果該Field的值在編譯時間就可以確定下來,那麼這個Field相當於"宏變數"。Java編譯器會在編譯時間直接把這個Field出現的地方替換成它的值,因此即使程式使用該靜態Filed,也不會導致該類的初始化。
下面舉例:
package Chapter18;public class CompileConstantTest{ public static void main(String[] args) { //訪問、輸出MyTest中的compileConstant Field System.out.println(MyTest.compileConstant); //1處 }}class MyTest{ static { System.out.println("靜態初始化塊..."); } //使用一個字串直接量為static final Field賦值 static final String compileConstant = "瘋狂Java講義";}
運行結果: