1. 程式人生 > 其它 >Java類載入器(ClassLoader)

Java類載入器(ClassLoader)

一、 類載入器

ClassLoader即常說的類載入器,其功能是用於從Class檔案載入所需的類,主要場景用於熱部署、程式碼熱替換等場景。 系統提供3種的類載入器:Bootstrap ClassLoader、Extension ClassLoader、Application ClassLoader

1.1 Bootstrap ClassLoader

啟動類載入器,一般由C++實現,是虛擬機器的一部分。該類載入器主要職責是將JAVA_HOME路徑下的\lib目錄中能被虛擬機器識別的類庫(比如rt.jar)載入到虛擬機器記憶體中。Java程式無法直接引用該類載入器

1.2 Extension ClassLoader

擴充套件類載入器,由Java實現,獨立於虛擬機器的外部。該類載入器主要職責將JAVA_HOME路徑下的\lib\ext目錄中的所有類庫,開發者可直接使用擴充套件類載入器。 該載入器是由sun.misc.Launcher$ExtClassLoader實現。

1.3 Application ClassLoader

應用程式類載入器,該載入器是由sun.misc.Launcher$AppClassLoader實現,該類載入器負責載入使用者類路徑上所指定的類庫。開發者可通過ClassLoader.getSystemClassLoader()方法直接獲取,故又稱為系統類載入器。當應用程式沒有自定義類載入器時,預設採用該類載入器。

ClassLoader.java

public static ClassLoader getSystemClassLoader() {
    initSystemClassLoader(); //初始化系統類載入器 【見下文】
    if (scl == null) {
        return null;
    }
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        ClassLoader ccl = getCallerClassLoader();
        if (ccl != null && ccl != scl && !scl.isAncestor(ccl)) {
            sm.checkPermission(SecurityConstants.GET_CLASSLOADER_PERMISSION);
        }
    }
    return scl;
}

  

系統類載入器初始化:

private static synchronized void initSystemClassLoader() {
    if (!sclSet) {
        if (scl != null)
            throw new IllegalStateException("recursive invocation");
        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
        if (l != null) {
            Throwable oops = null;
            scl = l.getClassLoader();
            try {
                scl = AccessController.doPrivileged(
                    new SystemClassLoaderAction(scl));
            } catch (PrivilegedActionException pae) {
                oops = pae.getCause();
                if (oops instanceof InvocationTargetException) {
                    oops = oops.getCause();
                }
            }
            if (oops != null) {
                if (oops instanceof Error) {
                    throw (Error) oops;
                } else {
                    throw new Error(oops);
                }
            }
        }
        sclSet = true;
    }
}

  

二、雙親委派模型

ClassLoader的雙親委派模型中,各個ClassLoader之間的關係是通過組合關係來複用父載入器。當一個ClassLoader收到來類載入的請求,首先把該請求委派該父類ClassLoader處理,當父類ClassLoader無法處理時,才由當前類ClassLoader來處理。對於每個ClassLoader這個方式,也就是父類的優先於子類處理類載入的請求,那麼也就是說任何一個請求第一次處理的便是最頂層的Bootstrap ClassLoader(啟動類載入器)。

類載入器的層級查詢順序依次為:啟動類載入器,擴充套件類載入器,系統類載入器。系統類載入器是預設的應用程式類載入器。

這樣的好處是不同層次的類載入器具有不同優先順序,比如所有Java物件的超級父類java.lang.Object,位於rt.jar,無論哪個類載入器載入該類,最終都是由啟動類載入器進行載入,保證安全。即使使用者自己編寫一個java.lang.Object類並放入程式中,雖能正常編譯,但不會被載入執行,保證不會出現混亂。那麼有人會繼續追問,如果自己再自定義一個類載入器來載入自己定義的java.lang.Object類呢? 這樣做也是不會成功的,虛擬機器將會丟擲一異常。

protected Class<?> loadClass(String name, boolean resolve)
    throws ClassNotFoundException
{
    synchronized (getClassLoadingLock(name)) {
        //檢查該類是否已經載入過
        Class c = findLoadedClass(name);
        if (c == null) {
            //如果該類沒有載入,則進入該分支
            long t0 = System.nanoTime();
            try {
                if (parent != null) {
                    //當父類的載入器不為空,則通過父類的loadClass來載入該類
                    c = parent.loadClass(name, false);
                } else {
                    //當父類的載入器為空,則呼叫啟動類載入器來載入該類
                    c = findBootstrapClassOrNull(name);
                }
            } catch (ClassNotFoundException e) {
                //非空父類的類載入器無法找到相應的類,則丟擲異常
            }

            if (c == null) {
                //當父類載入器無法載入時,則呼叫findClass方法來載入該類
                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) {
            //對類進行link操作
            resolveClass(c);
        }
        return c;
    }
}

  

當開發者需要自定義類載入器時,可通過覆寫loadClass()方法或者findClass()。

三、自定義類載入器

每一個ClassLoader都擁有自己獨立的類名稱空間,類是由ClassLoader將其載入到Java虛擬機器中,故類是由載入它的ClassLoader和該類本身一起確定其在Java 執行時環境的唯一性。故只有同一個ClassLoader載入的同一個類,才能算是Java 執行時環境中的相同的兩個類。哪怕是來自同一個Class檔案,即使被同一個虛擬機器載入的兩個類,只要ClassLoader不同,那麼也屬於不同的類。對於equals()、isinstanceof()等方法來判斷物件的相等或所屬關係都是需要基於同一個ClassLoader。

自定義類載入器示例:

package com.yuanhh.classloader;

import java.io.IOException;
import java.io.InputStream;

public class ClassLoadDemo{

    public static void main(String[] args) throws Exception {

        ClassLoader clazzLoader = new ClassLoader() {
            @Override
            public Class<?> loadClass(String name) throws ClassNotFoundException {
                try {
                    String clazzName = name.substring(name.lastIndexOf(".") + 1) + ".class";

                    InputStream is = getClass().getResourceAsStream(clazzName);
                    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);
                }
            }
        };

        String currentClass = "com.yuanhh.classloader.ClassLoadDemo";
        Class<?> clazz = clazzLoader.loadClass(currentClass);
        Object obj = clazz.newInstance();

        System.out.println(obj.getClass());
        System.out.println(obj instanceof com.yuanhh.classloader.ClassLoadDemo);
    }
}

  

上面程式碼的輸出結果:

class com.yuanhh.classloader.ClassLoadDemo
false

  

輸出結果的第一行,可以看出這個物件的確是com.yuanhh.classloader.ClassLoadDemo例項化的物件;但第二句是false,這是由於程式碼中的obj是由使用者自定義的類載入器clazzLoader來載入的,可通過obj.getClass().getClassLoader()獲取該物件的類載入器為com.yuanhh.classloader.ClassLoadDemo$xxx,而虛擬機器本身會由系統類載入器載入的類ClassLoadDemo,可通過ClassLoadDemo.class.getClassLoader()得其類載入器為sun.misc.Launcher$AppClassLoader@XXX。所以可得出結論:即使都是來自同一個Class檔案,載入器不同,仍然是兩個不同的類,所以返回值是false。

通過Class.forName()方法載入的類,採用的是系統類載入器。

四、 經典應用場景

  • Tomcat,類載入器架構,自己定義了多個類載入器,
    • 保證了同一個伺服器的兩個Web應用程式的Java類庫隔離;
    • 保證了同一個伺服器的兩個Web應用程式的Java類庫又可以相互共享;比如多個Spring組織的應用程式不能共享,會造成資源浪費;
    • 保證了伺服器儘可能保證自身的安全不受不受部署Web應用程式影響;
    • 支援JSP應用的伺服器,大多需要支援熱替換(HotSwap)功能。
  • OSGi(Open Service GateWay Initiative),是基於Java語言的動態模組化規範。已成為Java世界的“事實上”的模組化標準,最為熟悉的案例的Eclipse IDE。