天天看點

JVM優化:雙親委派模型

作者:日拱一卒程式猿

一、什麼是雙親委派

雙親委派模型工作過程是:如果一個類加載器收到類加載的請求,它首先不會自己去嘗試加載這個類,而是把這個 請求委派給父類加載器完成。每個類加載器都是如此,隻有當父加載器在自己的搜尋範圍内找不到指定的類時 (即 ClassNotFoundException ),子加載器才會嘗試自己去加載。

JVM優化:雙親委派模型

二、為什麼需要雙親委派模型?

為什麼需要雙親委派模型呢?假設沒有雙親委派模型,試想一個場景:

黑客自定義一個 java.lang.String 類,該 String 類具有系統的 String 類一樣的功能,隻是在某個函數 稍作修改。比如 equals 函數,這個函數經常使用,如果在這這個函數中,黑客加入一些“病毒代碼”。并且 通過自定義類加載器加入到 JVM 中。此時,如果沒有雙親委派模型,那麼 JVM 就可能誤以為黑客自定義的 java.lang.String 類是系統的 String 類,導緻“病毒代碼”被執行。

而有了雙親委派模型,黑客自定義的 java.lang.String 類永遠都不會被加載進記憶體。因為首先是最頂端的類加 載器加載系統的 java.lang.String 類,最終自定義的類加載器無法加載 java.lang.String 類。

或許你會想,我在自定義的類加載器裡面強制加載自定義的 java.lang.String 類,不去通過調用父加載器不就 好了嗎?确實,這樣是可行。但是,在 JVM 中,判斷一個對象是否是某個類型時,如果該對象的實際類型與待比較 的類型的類加載器不同,那麼會傳回false。

舉個栗子:

ClassLoader1 、 ClassLoader2 都加載 java.lang.String 類,對應Class1、Class2對象。那麼 Class1 對象不屬于 ClassLoad2 對象加載的 java.lang.String 類型。

三、如何實作雙親委派模型

雙親委派模型的原理很簡單,實作也簡單。每次通過先委托父類加載器加載,當父類加載器無法加載時,再自己加 載。其實 ClassLoader 類預設的 loadClass 方法已經幫我們寫好了,我們無需去寫。

幾個重要函數

loadClass 預設實作如下:

public Class loadClass(String name) throws ClassNotFoundException {

return loadClass(name, false);

}

再看看 loadClass(String name, boolean resolve) 函數:

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
    synchronized (getClassLoadingLock(name)) {
        // First, check if the class has already been loaded
        Class c = findLoadedClass(name);
        if (c == null) {
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    c = parent.loadClass(name, false);
                } else {
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                // ClassNotFoundException thrown if class not found
                // from the non-null parent class loader
            }
            if (c == null) {
                // If still not found, then invoke findClass in order
                // to find the class.
                long t1 = System.nanoTime();
                c = findClass(name);
                // this is the defining class loader; record the stats
                sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                sun.misc.PerfCounter.getFindClasses().increment();
            }
        }
        if (resolve) {
            resolveClass(c);
        }
        return c;
    }
}           

從上面代碼可以明顯看出, loadClass(String, boolean) 函數即實作了雙親委派模型!整個大緻過程如下:

1. 首先,檢查一下指定名稱的類是否已經加載過,如果加載過了,就不需要再加載,直接傳回。

2. 如果此類沒有加載過,那麼,再判斷一下是否有父加載器;如果有父加載器,則由父加載器加載(即 調用 parent.loadClass(name, false); ).或者是調用 bootstrap 類加載器來加載。

3. 如果父加載器及 bootstrap 類加載器都沒有找到指定的類,那麼調用目前類加載器的 findClass 方 法來完成類加載。

換句話說,如果自定義類加載器,就必須重寫 findClass 方法!

findClass 的預設實作如下:

protected Class findClass(String name) throws ClassNotFoundException {

throw new ClassNotFoundException(name);

}

可以看出,抽象類 ClassLoader 的 findClass 函數預設是抛出異常的。而前面我們知道, loadClass 在父加載 器無法加載類的時候,就會調用我們自定義的類加載器中的 findeClass 函數,是以我們必須要在 loadClass 這 個函數裡面實作将一個指定類名稱轉換為 Class 對象。

如果是讀取一個指定的名稱的類為位元組數組的話,這很好辦。但是如何将位元組數組轉為 Class 對象呢?很簡單, Java 提供了 defineClass 方法,通過這個方法,就可以把一個位元組數組轉為Class對象。

defineClass 主要的功能是:

将一個位元組數組轉為 Class 對象,這個位元組數組是 class 檔案讀取後最終的位元組數組。如,假設 class 文 件是加密過的,則需要解密後作為形參傳入 defineClass 函數。

defineClass 預設實作如下:

protected final Class defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {

return defineClass(name, b, off, len, null);

}

繼續閱讀