JVM必備基礎知識(二)-- 類加載器和雙親委派模型

本章內容是對《深刻理解Java虛擬機:JVM高級特性和最佳實踐》的理解和歸納。java

前言

在上文中咱們已經講了類的加載機制,這一章的主角就是類加載器和雙親委派模型了。mysql

類加載器

在Java虛擬機中,類加載器十分重要。每個類的加載,都須要經過一個類的加載器。可是若是咱們建立一個屬於本身的類加載器,這個時候會出現一個什麼樣的狀況呢? 接下來,咱們用代碼來進行驗證測試。sql

public class ClassLoaderTest {
    public static void main(String[] args) throws Exception {
        ClassLoader myLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
                    InputStream is = getClass().getResourceAsStream(fileName);
                    if (is == null) {
                        return super.loadClass(name);
                    }
                    byte[] b = new byte[is.available()];
                    is.read(b);
                    return defineClass(name, b, 0, b.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        // 由本身的類加載器建立對象
        Object obj = myLoader.loadClass("XuNiJi.ClassLoaderTest").newInstance();
        // 由系統提供的類加載器加載建立對象
        Object obj1 = ClassLoader.getSystemClassLoader().loadClass("XuNiJi.ClassLoaderTest").newInstance();
        System.out.println(obj instanceof ClassLoaderTest);
        System.out.println(obj1 instanceof ClassLoaderTest);
    }
}
複製代碼

從這裏想來已經可以看出了,由一個類加載器統一建立的類,才存在可比性。由於類加載器是擁有獨立的類名稱空間的。更簡單的說,就像上面的例子,若是不使用Java虛擬機提供的類加載器,你就會失去一大部分功能,好比 equals()isAssignableFrom()isInstance()instanceof。若是要相同,除非你直接在java源碼上動手腳。

雙親委派模型

第一個問題:爲何須要這個模型? 其實這個模型的提出,就是爲了解決類加載器可能不出現不一樣的問題。由於即使是相同的class,由不一樣的類加載器加載時,結果就是不一樣的。api

工做原理

雙親委派的工做流程很是簡單,這就跟以前文章裏的Android的事件分發機制同樣,向上傳遞,由上一層的加載器先行嘗試消費,若是上一層沒法完成這個任務,那麼子加載器就要由本身動手完成。ide

  1. 啓動類加載器:負責加載/lib下的類。
  2. 擴展類加載器:負責加載/lib/ext下的類。
  3. 系統類加載器/應用程序類加載器:ClassLoader.getSystemClassLoader返回的就是它。

經過上圖咱們能夠知道,子加載器不斷的給上一層加載器傳遞加載請求,那麼這個時候啓動類加載器勢必是接受到過所有的加載請求的。若是不信,咱們就用源碼來證實。函數

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // 判斷Class是否被加載過
            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
                    // 說明父類沒法完成加載
                }
                // 這個時候c依舊爲null,說明父類加載不了
                // 那沒有辦法,只能子加載器本身效勞了
                if (c == null) {
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
}
複製代碼

講完了他的工做原理,天然就要知道,他可以如何被破壞的了。post

破壞雙親委派模型

第二個問題,爲何要破壞雙親委派? 拿最簡單的例子,在上文中咱們,提到過各個資源的加載範圍,可是Driver做爲後來才加入的一個接口,他的不少api是由第三方服務商開發的。那麼這個時候,破壞雙親委派就有了他的用武之地了,固然這只是他的用處之一。學習

下面來介紹,他是如何破壞雙親委派的。 先看看咱們平時都是怎麼用的。(固然這是很基礎的寫法了,由於如今池的概念加深,因此不少事情都已經被封裝了。)測試

String url = "jdbc:mysql://localhost:3306/db";
Connection conn = DriverManager.getConnection(url, "root", "root"); 
複製代碼

上面很明顯就能看出這件事情就是關於DriverManager展開的了。ui

static {
    loadInitialDrivers();
    println("JDBC DriverManager initialized");
}
複製代碼

這裏根據前一章的內容先要對DriverManager進行初始化,也就是調用了一個loadInitialDrivers()函數。

private static void loadInitialDrivers() {
        .....
        AccessController.doPrivileged(new PrivilegedAction<Void>() {
            public Void run() {

                ServiceLoader<Driver> loadedDrivers = ServiceLoader.load(Driver.class);// 1
                Iterator<Driver> driversIterator = loadedDrivers.iterator();

                try{
                    while(driversIterator.hasNext()) {
                        driversIterator.next();
                    }
                } catch(Throwable t) {
                // Do nothing
                }
                return null;
            }
        });
        .....
}
複製代碼

從這一小段中,咱們關注註釋1可以知道他專門去訪問了一個ServiceLoader的類,點進去以後咱們可以發現這麼三段代碼。

// 1
public static <S> ServiceLoader<S> load(Class<S> service) {
        ClassLoader cl = Thread.currentThread().getContextClassLoader();
        return ServiceLoader.load(service, cl);
}

// 2
public static <S> ServiceLoader<S> load(Class<S> service, ClassLoader loader){
        return new ServiceLoader<>(service, loader);
    }

// 3
private ServiceLoader(Class<S> svc, ClassLoader cl) {
        service = Objects.requireNonNull(svc, "Service interface cannot be null");
        loader = (cl == null) ? ClassLoader.getSystemClassLoader() : cl;
        acc = (System.getSecurityManager() != null) ? AccessController.getContext() : null;
        reload();
}
複製代碼

由1 --> 2 --> 3的順序按部就班,你是否已經和我關注到一個問題了!! ClassLoader.getSystemClassLoader(),看到這個函數了嗎,我在上文提到過,這個函數咱們得到的類加載器將會是應用程序類加載器。也就是說咱們的任務不會再向上傳遞了,到頭就是到了應用程序類加載器這個位置,那麼雙親委派模型也就破壞了。

以上就是打破雙親委派的方法之一的介紹了。

溯源ClassLoader.getSystemClassLoader()

爲何說咱們調用的是應用程序類加載器呢? 接下來直接從源碼來解析了。 首先就是調用getSystemClassLoader()這個函數了

這張圖裏咱們只用關注圈紅的函數。

而後在initSystemClassLoader()函數中調用了一個Launcher的類。

Launcher整個類的建立,想來讀者也已經看到loader這個變量了,經過getAppClassLoader()這個函數所建立的loader也就是咱們口中所說的應用程序類加載器了。

以上就是個人學習成果,若是有什麼我沒有思考到的地方或是文章內存在錯誤,歡迎與我分享。


相關文章推薦:

JVM必備基礎知識(一)-- 類的加載機制

JVM必備基礎知識(三)-- GC垃圾回收機制

相關文章
相關標籤/搜索