JDK源碼(十七):ClassLoader

顧名思義,類加載器(ClassLoader)用來加載 Java 類到 Java 虛擬機中。一般來說,Java 虛擬機使用 Java 類的方式如下:Java 源程序(.java 文件)在經過 Java 編譯器編譯之後就被轉換成 Java 字節代碼(.class 文件)。類加載器負責讀取 Java 字節代碼,並轉換成 java.lang.Class類的一個實例。每個這樣的實例用來表示一個 Java 類。通過此實例的 newInstance()方法就可以創建出該類的一個對象。實際的情況可能更加複雜,比如 Java 字節代碼可能是通過工具動態生成的,也可能是通過網絡下載的。

ClassLoader是負責加載類的對象。ClassLoader是一個抽象類。給定類的二進制名稱,ClassLoader應嘗試查找或生成構成類定義的數據。典型的策略是將名稱轉換爲文件名,然後從文件系統中讀取該名稱的“類文件”。

每個Class類對象都包含一個Class.getClassLoader()引用,指向定義它的ClassLoader。

數組類的對象不是由ClassLoader創建的,而是根據Java運行時的要求自動創建的。由Class.getClassLoader()返回的數組類的ClassLoader與元素類型的ClassLoader相同;如果元素類型是基元類型,則數組類沒有ClassLoader。

應用程序實現ClassLoader的子類,以擴展Java虛擬機動態加載類的方式。ClassLoader通常可由安全管理器用於指定安全域。

ClassLoader類使用委託模型來搜索類和資源。ClassLoader的每個實例都有一個相關的父ClassLoader。當請求查找類或資源時,ClassLoader實例將在試圖查找類或資源本身之前,將對該類或資源的搜索委託給其父ClassLoader。虛擬機的內置ClassLoader稱爲“引導類加載器”,它本身沒有父類,但可以充當ClassLoader實例的父類。

支持類的併發加載的ClassLoader稱爲支持並行的ClassLoader,需要在類初始化時通過調用{ClassLoader.registeraspallelcapable}方法註冊它們自己。注意,默認情況下,ClassLoader類註冊爲支持並行。但是,如果其子類具有並行能力,則仍需要註冊它們自己。在委託模型不是嚴格分層的環境中,ClassLoader需要具有並行能力,否則類裝入可能導致死鎖,因爲裝入器鎖在類裝入過程的持續時間內保持。

通常,Java虛擬機以依賴於平臺的方式從本地文件系統加載類。例如,在UNIX系統上,虛擬機從由CLASSPATH環境變量定義的目錄加載類。

但是,有些類可能不是源於文件;它們可能源於其他源,例如網絡,或者可以由應用程序構造。方法{defineClass(String,byte[],int,int)}將字節數組轉換爲類的實例。這個新定義的類的實例可以使用{class.newInstance}創建。

Java 中的類加載器大致可以分成兩類,一類是系統提供的,另外一類則是由 Java 應用開發人員編寫的。系統提供的類加載器主要有下面三個:

  1. 引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader

  2. 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。

  3. 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。一般來說,Java 應用的類都是由它來完成加載的。可以通過 ClassLoader.getSystemClassLoader()來獲取它。

開發人員可以通過繼承 java.lang.ClassLoader類的方式實現自己的類加載器,以滿足一些特殊的需求。

除了引導類加載器之外,所有的類加載器都有一個父類加載器。通過  getParent()方法可以得到。

public static void main(String[] args) {
        ClassLoader classLoader = ClassLoaderDemo.class.getClassLoader();
        if (classLoader != null){
            System.out.println(classLoader.getParent().toString());
            //輸出:sun.misc.Launcher$ExtClassLoader@379619aa
        }
    }

ClassLoader中方法很多,但是與加載類相關的方法有下面這個幾個:

getParent()

@CallerSensitive
    public final ClassLoader getParent() {
        if (parent == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            // 權限檢查
            checkClassLoaderPermission(parent, Reflection.getCallerClass());
        }
        return parent;
    }

loadClass(String name)

public Class<?> loadClass(String name) throws ClassNotFoundException {
        return loadClass(name, false);
    }
    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) {
                        c = parent.loadClass(name, false);
                    } else {
						//返回引導類加載器加載的類;如果找不到,則返回null
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // 如果仍然找不到,則調用findClass找到該類
                    long t1 = System.nanoTime();
					//findClass需要重寫
                    c = findClass(name);

                    //這是定義類裝入器;記錄統計信息
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
				//鏈接指定的 Java 類
                resolveClass(c);
            }
            return c;
        }
    }

findClass(String name)

這個方法需要子類進行重寫。

findLoadedClass(String name)

protected final Class<?> findLoadedClass(String name) {
        if (!checkName(name))
            return null;
        return findLoadedClass0(name);
    }

    private native final Class<?> findLoadedClass0(String name);

defineClass(String name, byte[] b, int off, int len)

    protected final Class<?> defineClass(String name, byte[] b, int off, int len)
        throws ClassFormatError
    {
        return defineClass(name, b, off, len, null);
    }
	
    protected final Class<?> defineClass(String name, byte[] b, int off, int len,
                                         ProtectionDomain protectionDomain)
        throws ClassFormatError
    {
		//確定保護域
        protectionDomain = preDefineClass(name, protectionDomain);
        String source = defineClassSourceLocation(protectionDomain);
		//定義類
        Class<?> c = defineClass1(name, b, off, len, protectionDomain, source);
		//設置證書
        postDefineClass(c, protectionDomain);
        return c;
    }

開發自己的類加載器

public class CustomClassLoader extends ClassLoader {

        private String rootDir;

        public CustomClassLoader(String rootDir) {
            this.rootDir = rootDir;
        }
        //重寫findClass
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] classData = getClassData(name);
            if (classData == null) {
                throw new ClassNotFoundException();
            }
            else {
                return defineClass(name, classData, 0, classData.length);
            }
        }

        private byte[] getClassData(String className) {
            String path = classNameToPath(className);
            //獲取文件中的類內容

            //獲取文件中的類內容
            return null;
        }

        private String classNameToPath(String className) {
            return rootDir + File.separatorChar
                    + className.replace('.', File.separatorChar) + ".class";
        }
    }

類加載器與web容器

對於運行在 Java EE™容器中的 Web 應用來說,類加載器的實現方式與一般的 Java 應用有所不同。不同的 Web 容器的實現方式也會有所不同。以  Tomcat 來說,每個 Web 應用都有一個對應的類加載器實例。該類加載器也使用代理模式,所不同的是它是首先嚐試去加載某個類,如果找不到再代理給父類加載器。這與一般類加載器的順序是相反的。這是 Java Servlet 規範中的推薦做法,其目的是使得 Web 應用自己的類的優先級高於 Web 容器提供的類。這種代理模式的一個例外是:Java 核心庫的類是不在查找範圍之內的。這也是爲了保證 Java 核心庫的類型安全。

絕大多數情況下,Web 應用的開發人員不需要考慮與類加載器相關的細節。下面給出幾條簡單的原則:

  • 每個 Web 應用自己的 Java 類文件和使用的庫的 jar 包,分別放在 WEB-INF/classes和 WEB-INF/lib目錄下面。

  • 多個應用共享的 Java 類文件和 jar 包,分別放在 Web 容器指定的由所有 Web 應用共享的目錄下面。

  • 當出現找不到類的錯誤時,檢查當前類的類加載器和當前線程的上下文類加載器是否正確。

更多精彩內容請關注微信公衆號:

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章