不瞭解JVM的類加載機制你也能夠coding,可是當你瞭解以後,可讓你在coding的時候避免不少坑,本文將以一道常見的面試題去剖析一下。本文參考深刻理解Java虛擬機(第2版)。java
1public class ClassLoadTest {
2 private static ClassLoadTest test = new ClassLoadTest();
3
4 static int x;
5 static int y = 0;
6
7 public ClassLoadTest() {
8 x++;
9 y++;
10 }
11
12 public static void main(String[] args) {
13 System.out.println(test.x);
14 System.out.println(test.y);
15 }
16}
複製代碼
這裏你們能夠先猜想一下答案,可能結果會出乎你的意料~面試
先用一個圖簡單的描述一下類加載的這個過程
網絡
這個過程至關於從本地或者網絡端去讀取一個字節流,而後將一些靜態儲存結構轉換成方法區中運行時期的數據,最後生成一個表明這個類的Class對象,做爲方法區訪問這個類的入口。
例如:app
針對上述例子,這裏是加載一個ClassLoadTest.class
對象。佈局
要理解這個環節並非很難,一個東西要放到JVM上去運行,我們確定得對其進行一些過濾,不能啥都往上丟,這裏的驗證簡單的舉幾個例子:spa
這個過程至關於給類變量分配內存並設置變量初始值的階段,這些變量所使用的內存都將在方法區中進行分配。線程
針對上述例子:3d
1test = null;
2x = 0;
3y = 0;
複製代碼
注意:這裏有個特殊狀況,若是該字段被final
修飾,那麼在準備階段改字段就會被設置成我們自定義的值。public static final int value = 11
,在準備階段就會直接賦值11,並非該變量的初始值。代理
將符號引用轉換成直接引用的過程。這裏有兩個名詞符號引用和直接引用。指針
- 符號引用:符號引用與虛擬機的佈局無關,甚至引用的目標不必定加載到了內存中。符號能夠是任何形式的字面量,只要使用時可以準確的定位到目標便可。
- 直接引用:直接引用能夠直接指向目標的指針、相對偏移量或是一個能間接定位到目標的句柄。直接引用與虛擬機佈局有關,若是有了直接引用,那麼引用的目標一定已經在內存中存在。
而解析過程又會針對類、字段、方法進行解析,解析失敗則會拋出相應的異常。例如在解析時發現沒有訪問權限會拋出java.lang.IllegalAccessException
異常,查詢不到引用字段會拋出java.lang.NoSuchFieldException
異常,查詢不到方法會拋出java.lang.NoSuchMethodException
異常等等。
在準備階段,變量已經賦值過系統要求的默認值,在初始化階段,則會根據程序制定的主觀計劃去初始化類變量和其餘資源。這句話聽起來有些繞口,根據上述例子,實際上就是:
1test = new ClassLoadTest();// x = 1;y =1
2y = 0;
複製代碼
這個過程,因爲x
我們本身並無去設定一個值,因此初始化階段它不會發生任何改變,可是y
我們有設定一個值0,因此最後形成最終結果爲x = 1;y = 0
。
ps:在同一個類加載器下,一個類只會初始化一次。多個線程同時初始化一個類,只有一個線程能正常初始化,其餘線程都會進行阻塞等待,直到活動線程執行初始化方法完畢。
對於上面這個面試題,我們用流程圖簡單的描述一下: