JAVA啓動後,是通過JVM各級ClassLoader來加載各個類到內存。爲了更加了解加載過程,我經過分析和寫了一個簡單的ClassLoader來粗淺的分析它的原理。java
JVM的ClassLoader分三層,分別爲Bootstrap ClassLoader,Extension ClassLoader,System ClassLoader,他們不是類繼承的父子關係,是邏輯上的上下級關係。mysql
Bootstrap ClassLoader是啓動類加載器,它是用C++編寫的,從%jre%/lib目錄中加載類,或者運行時用-Xbootclasspath指定目錄來加載。sql
Extension ClassLoader是擴展類加載器,從%jre%/lib/ext目錄加載類,或者運行時用-Djava.ext.dirs制定目錄來加載。編程
System ClassLoader,系統類加載器,它會從系統環境變量配置的classpath來查找路徑,環境變量裏的.表示當前目錄,是經過運行時-classpath或-Djava.class.path指定的目錄來加載類。數組
通常自定義的Class Loader能夠從java.lang.ClassLoader繼承,不一樣classloader加載相同的類,他們在內存也不是相等的,即它們不能互相轉換,會直接拋異常。java.lang.ClassLoader的核心加載方法是loadClass方法,如:緩存
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 = findBootstrapClass0(name); } } catch (ClassNotFoundException e) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; }
經過上面加載過程,咱們能知道JVM默認是雙親委託加載機制,即首先判斷緩存是否有已加載的類,若是緩存沒有,但存在父加載器,則讓父加載器加載,若是不存在父加載器,則讓Bootstrap ClassLoader去加載,若是父類加載失敗,則調用本地的findClass方法去加載。tomcat
雙親委託機制的做用是防止系統jar包被本地替換,由於查找方法過程都是從最底層開始查找。 所以,通常咱們自定義的classloader都須要採用這種機制,咱們只須要繼承java.lang.ClassLoader實現findclass便可,若是須要更多控制,自定義的classloader就須要重寫loadClass方法了,好比tomcat的加載過程,這個比較複雜,能夠經過其餘文檔資料查看相關介紹。app
各個ClassLoader加載相同的類後,他們是不互等的,這個當涉及多個ClassLoader,而且有經過當前線程上線文獲取ClassLoader後轉換特別須要注意,能夠經過線程的setContextClassLoader設置一個ClassLoader線程上下文,而後再經過Thread.currentThread().getContextClassLoader()獲取當前線程保存的Classloader。可是自定義的類文件,放到Bootstrap ClassLoader加載目錄,是不會被Bootstrap ClassLoader加載的,由於做爲啓動類加載器,它不會加載本身不熟悉的jar包的,而且類文件必須打包成jar包放到加載器加載的根目錄,纔可能被擴展類加載器所加載。jvm
Bootstrap ClassLoader、Extension ClassLoader、App ClassLoader三者的關係以下:this
Bootstrap ClassLoader是Extension ClassLoader的parent,Extension ClassLoader是App ClassLoader的parent。
可是這並非繼承關係,只是語義上的定義,基本上,每個ClassLoader實現,都有一個Parent ClassLoader。
能夠經過ClassLoader的getParent方法獲得當前ClassLoader的parent。Bootstrap ClassLoader比較特殊,由於它不是java class因此Extension ClassLoader的getParent方法返回的是NULL。
因爲一些特殊的需求,咱們可能須要定製ClassLoader的加載行爲,這時候就須要自定義ClassLoader了.
自定義ClassLoader須要繼承ClassLoader抽象類,重寫findClass方法,這個方法定義了ClassLoader查找class的方式。
主要能夠擴展的方法有:
findClass 定義查找Class的方式
defineClass 將類文件字節碼加載爲jvm中的class
findResource 定義查找資源的方式
現已有的ClassLoader實現有以下幾種:
java.net.URLClassLoader
java.security.SecureClassLoader
java.rmi.server.RMIClassLoader
sun.applet.AppletClassLoader
Extension ClassLoader 和 App ClassLoader都是java.net.URLClassLoader的子類。
這個是URLClassLoader的構造方法:
public URLClassLoader(URL[] urls, ClassLoader parent) public URLClassLoader(URL[] urls)
urls參數是須要加載的ClassPath url數組,能夠指定parent ClassLoader,不指定的話默認以當前調用類的ClassLoader爲parent。
因爲classloader 加載類用的是全盤負責委託機制。所謂全盤負責,便是當一個classloader加載一個Class的時候,這個Class所依賴的和引用的全部 Class也由這個classloader負責載入,除非是顯式的使用另一個classloader載入。
因此,當咱們自定義的classloader加載成功了com.company.MyClass之後,MyClass裏全部依賴的class都由這個classLoader來加載完成。
Class.forName() 與 load.loadClass()的區別:
Class.forName("xx.xx")等同於Class.forName("xx.xx",true,CALLClass.class.getClassLoader()),第二個參數(bool)表示裝載類的時候是否初始化該類,即調用類的靜態塊的語句及初始化靜態成員變量。
ClassLoader loader = Thread.currentThread.getContextClassLoader(); //也能夠用(ClassLoader.getSystemClassLoader())
Class cls = loader.loadClass("xx.xx"); //這句話沒有執行初始化,其實與Class.forName("xx.xx",false,loader)是一致的,只是loader.loadClass("xx.xx")執行的是更底層的操做。
只有執行cls.NewInstance()纔可以初始化類,獲得該類的一個實例
Class的裝載分了三個階段,loading,linking和initializing,分別定義在The Java Language Specification的12.2,12.3和12.4。
Class.forName(className) 其實是調用Class.forName(className, true, this.getClass().getClassLoader())。注意第二個參數,是指Class被loading後是否是必須被初始化。
ClassLoader.loadClass(className)實際上調用的是ClassLoader.loadClass(name, false),第二個參數指出Class是否被link。
區別就出來了。Class.forName(className)裝載的class已經被初始化,而ClassLoader.loadClass(className)裝載的class尚未被link。
forName支持數組類型,loadClass不支持數組
通常狀況下,這兩個方法效果同樣,都能裝載Class。但若是程序依賴於Class是否被初始化,就必須用Class.forName(name)了。
例如,在JDBC編程中,常看到這樣的用法,Class.forName("com.mysql.jdbc.Driver"),若是換成了 getClass().getClassLoader().loadClass("com.mysql.jdbc.Driver"),就不行。
爲何呢?打開com.mysql.jdbc.Driver的源代碼看看,
// // Register ourselves with the DriverManager // static { try { java.sql.DriverManager.registerDriver(new Driver()); } catch (SQLException E) { throw new RuntimeException("Can't register driver!"); } }
原來,Driver在static塊中會註冊本身到java.sql.DriverManager。而static塊就是在Class的初始化中被執行。因此這個地方就只能用Class.forName(className)。