淺析java類加載器ClassLoader


作為一枚java猿,了解類加載器是有必要的,無論是針對面試還是自我學習。

本文從JDK提供的ClassLoader、委托模型以及如何編寫自定義的ClassLoader三方面對ClassLoader做一個簡要的總結。

 

JDK中提供的ClassLoader

1. Bootstrap ClassLoader

  Bootstrap加載器是用C++語言寫的,它是在Java虛擬機啟動后初始化的,它主要負責加載%JAVA_HOME%/jre/lib以及%JAVA_HOME%/jre/classes中的類,是最頂級的ClassLoader。

2. Ext ClassLoader

  Ext ClassLoader是用java寫的,且它的父加載器是Bootstrap,具體來說就是sun.misc.Launcher$ExtClassLoader,Ext ClassLoader主要加載%JAVA_HOME%/jre/lib/ext,此路徑下的所有classes目錄以及java.ext.dirs系統變量指定的路徑中的類庫。

3. App ClassLoader 

  系統類加載器,負責加載應用程序classpath目錄下的所有jar和class文件。它的父加載器為Ext ClassLoader。

 

具體關系如下圖:

委托模型

進入官方的Java doc里看到ClassLoader類的一段說明:

The ClassLoader class uses a delegation model to search for classes and resources. Each instance of ClassLoader has an associated parent class loader. When requested to find a class or resource, a ClassLoader instance will delegate the search for the class or resource to its parent class loader before attempting to find the class or resource itself. The virtual machine's built-in class loader, called the "bootstrap class loader", does not itself have a parent but may serve as the parent of a ClassLoader instance.

ClassLoader類使用一種委托模型來查找類和資源。每個ClassLoader實例都會關聯1個父ClassLoader。當需要查詢類和資源的時候,一個ClassLoader實例在查詢類或資源之前會先委托給它的父ClassLoader去查詢。Bootstrap ClassLoader是最頂層的加載器,並且可以作為其它ClassLoader實例的父ClassLoader。

由此看見,這個“委托模型”的安全性是很高的,Bootstrap是最頂層的加載器,這樣比如加載 java.lang.String 的時候,永遠都會被Bootstrap加載(Bootstrap ClassLoader會加載%JAVA_HOME%/jre/lib中rt.jar里的String類)。 這樣用戶自定義的java.lang.String永遠都不會被加載,這樣就避免了多個java.lang.String造成的混亂現象。

 

下面通過jdk里的ClassLoader源碼來驗證一下查找過程:

  protected synchronized Class<?> loadClass(String name, boolean resolve)
	throws ClassNotFoundException
    {
	// First, check if the class has already been loaded
	Class c = findLoadedClass(name); //先查找這個類是否已經加載過,每個加載器都有自己的緩存
	if (c == null) {
	    try {
		if (parent != null) { //父加載器存在的話先使用父加載器加載
		    c = parent.loadClass(name, false);
		} else {
		    c = findBootstrapClassOrNull(name); //沒有父加載器的話使用bootstrap加載
		}
	    } 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.
	        c = findClass(name); //如果父加載器沒有找到,那么自身查找
	    }
	}
	if (resolve) {
	    resolveClass(c);
	}
	return c;
    }

通過代碼看,這里的查找過程符合委托模型。

如何編寫自定義的ClassLoader

編寫自定義的ClassLoader注意2點即可:

1. 想遵循委托模型的話重寫findClass方法即可。

2. 不遵循委托模型的話重寫loadClass。

 

其他:defineClass方法把字節數組b中的內容轉換成Java 類,返回的結果是 java.lang.Class類的實例。這個方法被聲明為final的。該方法也是jvm預留給我們處理ClassLoader與類文件關系的入口。

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

 

下面就來寫一個基於文件系統的ClassLoader:

先來看下定義的一個類:

package org.format.classloader;

public class Obj {
  
    @Override
    public String toString() {
        return "org.format.classloader.Obj";
    }
    
}

自定義的ClassLoader:

public class FileSystemClassLoader extends ClassLoader{

    private String directory;
    
    public FileSystemClassLoader(String directory) {
        this.directory = directory;    
    }
  
    @Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        byte[] clsBytes = getClassBytes(name);
        ifclsBytes == null)
            throw new ClassNotFoundException();
        return defineClass(name, clsBytes, 0, clsBytes.length);
    }
    
    private byte[] getClassBytes(String name) {
        String location = getClassLoc(name);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        FileInputStream fis = null;
        try {
            fis = new FileInputStream(location);
            byte[] buffer = new byte[4096];
            int readLen = 0;
            while( (readLen = fis.read(buffer)) != -1 ) {
                baos.write(buffer, 0, readLen);
            }
            baos.flush();
            return baos.toByteArray();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if(fis != null) {
                try {
                    fis.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return null;
    }

    private String getClassLoc(String name) {
        return this.directory + File.separatorChar + name.replace('.', File.separatorChar) + ".class";
    }
    
}

在測試之前,使用javac命令將Obj.java編譯成Obj.class,然后放與/tmp/java/classloader目錄下。

下面就用自定義的ClassLoader來進行幾個測試:

FileSystemClassLoader fscl = new FileSystemClassLoader("/tmp/java/classloader");
try {
    System.out.println(fscl.loadClass("org.format.classloader.Obj").newInstance());
} catch (Exception e) {
    e.printStackTrace();
}    

很明顯,自定義的FileSystemClassLoader加載到了自定義的Obj類。

FileSystemClassLoader fscl1 = new FileSystemClassLoader("/tmp/java/classloader");
FileSystemClassLoader fscl2 = new FileSystemClassLoader("/tmp/java/classloader");
try {
    Class cls1 = fscl1.loadClass("org.format.classloader.Obj");
    Class cls2 = fscl2.loadClass("org.format.classloader.Obj");
    System.out.println("class1: " + cls1);
    System.out.println("class2: " + cls2);
    System.out.println("class1 == class2? " + (cls1 == cls2));
} catch (Exception e) {
    e.printStackTrace();
}    

 

我們可以,使用不同的類加載器加載同一class文件得出的Class對象是不一樣的。

這是因為Java 虛擬機不僅要看類的全名是否相同,還要看加載此類的類加載器是否一樣。只有兩者都相同的情況,才認為兩個類是相同的。

參考資料

http://imtiger.net/blog/2009/11/09/java-classloader/

http://www.ibm.com/developerworks/cn/java/j-lo-classloader/

http://jiangbo.me/blog/2012/02/14/jetty-classloader/


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM