由一道面試題理解類載入機制

mars_jun發表於2019-03-17

前言

不瞭解JVM的類載入機制你也可以coding,但是當你瞭解之後,可以讓你在coding的時候避免很多坑,本文將以一道常見的面試題去剖析一下。本文參考 深入理解Java虛擬機器(第2版) 。

 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}
複製程式碼

這裡大家可以先猜測一下答案,可能結果會出乎你的意料~

類載入過程

先用一個圖簡單的描述一下類載入的這個過程

image.png
載入

這個過程相當於從本地或者網路端去讀取一個位元組流,然後將一些靜態儲存結構轉換成方法區中執行時期的資料,最後生成一個代表這個類的Class物件,作為方法區訪問這個類的入口。

例如:

  • 我們們可以通過一個類的全限定名去載入類

  • 通過jar、war包去載入類

  • 通過http請求去第三方平臺上拉取指定的類來載入

  • 執行時計算生成,例如Cglib動態代理等等

針對上述例子,這裡是載入一個 ClassLoadTest.class 物件。

驗證

要理解這個環節並不是很難,一個東西要放到JVM上去執行,我們們肯定得對其進行一些過濾,不能啥都往上丟,這裡的驗證簡單的舉幾個例子:

  • 檔案格式的驗證:

    ①是否以魔數0xCAFEBABE開頭;

    ②主次版本號是否在當前虛擬機器處理範圍內;

    ③常量池中的常量是否有不被支援的常量型別等等。

  • 後設資料的驗證:

    ①這個類是否有父類;

    ②這個類的父類是否繼承了不被允許繼承的類(final修飾的類);

    ③這個類不是抽象類,是否實現了所有介面中要實現的方法等等。

  • 位元組碼的驗證:

    ①保證跳轉指令不會跳轉到方法體以外的位元組碼指令上;

    ②保證方法體中的型別轉換是有效的等等。

  • 符號引用的驗證:

    ①能否通過類的全限定名去找到對應的類;

    ②符號引用中的類、欄位、方法是否可以被當前類訪問等等。

準備過程

這個過程相當於給類變數分配記憶體並設定變數初始值的階段,這些變數所使用的記憶體都將在方法區中進行分配。

針對上述例子:

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:在同一個類載入器下,一個類只會初始化一次。多個執行緒同時初始化一個類,只有一個執行緒能正常初始化,其他執行緒都會進行阻塞等待,直到活動執行緒執行初始化方法完畢。

總結

對於上面這個面試題,我們們用流程圖簡單的描述一下:

image

相關文章