一、前言
首先,小小測試,看是否已經掌握了JVM類載入的過程
1.1、測試一
class Singleton { private static Singleton sin = new Singleton(); public static int counter1; public static int counter2 = 0; private Singleton() { counter1++; counter2++; } public static Singleton getInstance() { return sin; } } public class Test { public static void main(String[] args) { Singleton sin = Singleton.getInstance(); System.out.println(sin.counter1); System.out.println(sin.counter2); } }
輸出結果為:
1 3 ?
1 0 ?
0 1?
1.2、測試二:
class Singleton { public static int counter1; public static int counter2 = 2; private static Singleton sin = new Singleton(); private Singleton() { counter1++; counter2++; } public static Singleton getInstance() { return sin; } } public class Test { public static void main(String[] args) { Singleton sin = Singleton.getInstance(); System.out.println(sin.counter1); System.out.println(sin.counter2); } }
輸出結果為:
1 3 ?
1 2 ?
0 1?
正確的輸出結果如下:
測試一的結果為:1 0
測試二的結果為:1 3
如果對結果有疑惑或者不知道原因的園友需要了解類載入器的具體細節,相信看了本篇文章,一定會解開您的疑惑。廢話不多說,直奔主題。
二、背景知識
2.1、Java虛擬機器通過裝(加)載、連線、初始化一個Java型別,使該型別可以被正在執行的Java程式所使用。
①裝(加)載類的載入指的是將類的.class檔案中的二進位制資料讀入到記憶體中,將其放在執行時資料區的方法區中,然後在堆區建立一個java.lang.Class物件,用來封裝類在方法區內的資料結構,之後可以用Class物件進行相關的反射操作。
②連線分為三個子步驟
驗證:確保被載入的類的正確性
準備:為類的靜態變數分配記憶體,並將其初始化為預設值
解析: 把類中的符號引用轉換為直接引用
③初始化為為類的靜態變數賦予正確的初始值
如下為流程圖:
2.2、關於初始化的細節
所有的Java虛擬機器實現必須在每個類或介面被Java程式“首次主動使用”時才初始化他們,下面六種情況符合首次主動使用要求。
① 建立類的例項
②訪問某個類或介面的非常量靜態域,或者對該非常量靜態域賦值
③ 呼叫類的靜態方法
④反射(如Class.forName(“com.test.Test”)(其中Test為一個類),而Test.class就不是首次使用)
⑤初始化一個類的子類
⑥Java虛擬機器啟動時被標明為啟動類的類(Test)(Test為包含了程式入口main方法的類)
2.3、如論如何,如果一個型別在其首次主動使用之前還沒有被裝(加)載和連線的話,那它必須在此時進行載入和連線,這樣它才能夠被初始化。
三、問題分析
3.1、讀到這裡應該可以分析出為什麼之前的程式會輸出那樣的結果,下面我們來一起分析一下整個過程。
①對於測試一的結果分析
首先在main中呼叫了Singleton的getInstance類靜態方法,符合第③條,需要初始化類,即初始化Singleton,首先需要裝(加)載和連線,從硬碟中載入進記憶體,然後進入連線的驗證,沒有問題,OK,進入準備階段,此時,將類變數sin、counter1、counter2分配記憶體,並初始化預設值null、0、0。緊接著,將符號引用轉化為直接引用(暫 時不不要太瞭解,程式中就是將Singleton符號轉化為在記憶體裡直接對地址的引用,這樣就可以通過地址直接訪問Singleton型別了)。接下來是初始化過程,首先呼叫sin的構造方法,然後將counter1、counter2分別+1,即counter1 = 1,counter2 = 1,完成了sin靜態變數的初始化,然後初始化靜態變數counter1,但是由於沒有對counter1賦初值,所以counter1還是為1,然而,程式中對counter2進行了賦初值操作,即將counter2賦值為0。這樣便完成了型別的初始化,得到的counter1和counter2的結果為1和0,分析完畢。
結果分析流程圖如下:
②同理也可以對測試二進行結果分析
四、總結
整個類的初始化三個階段細節過程遠比這個複雜得多,但是我們仍可以通過類的整個巨集觀流程來分析出正確的結果,對過程的分析也有助於我們寫出正確的程式。真正做到知其然知其所以然。也感謝各位園友的觀看,謝謝。