JAVA之ClassLoader

Java的類加載器在sun.misc.Launcher中初始化。

    public Launcher() {
        ExtClassLoader localExtClassLoader;
        try {
          localExtClassLoader = ExtClassLoader.getExtClassLoader();
        }
        catch(IOException localIOException1) {
          throw new InternalError("Could not create extension class loader", localIOException1);
        }

        try {
          this.loader = AppClassLoader.getAppClassLoader(localExtClassLoader);
        }
        catch(IOException localIOException2) {
          throw new InternalError("Could not create application class loader", localIOException2);
        }

        Thread.currentThread().setContextClassLoader(this.loader);

        String str = System.getProperty("java.security.manager");
        if(str != null) {
          SecurityManager localSecurityManager = null;
          if(( "".equals(str) ) || ( "default".equals(str) ))
            localSecurityManager = new SecurityManager();
          else
            try {
              localSecurityManager = (SecurityManager) this.loader.loadClass(str).newInstance();
            }
            catch(IllegalAccessException localIllegalAccessException) {}
            catch(InstantiationException localInstantiationException) {}
            catch(ClassNotFoundException localClassNotFoundException) {}
            catch(ClassCastException localClassCastException) {}
          if(localSecurityManager != null)
            System.setSecurityManager(localSecurityManager);
          else
            throw new InternalError("Could not create SecurityManager: " + str);
        }
    }

ExtClassLoader通過ExtClassLoader.getExtClassLoader()初始化。
AppClassLoader通過AppClassLoader.getAppClassLoader(ExtClassLoader)初始化。

    public static ExtClassLoader getExtClassLoader() throws IOException {
      File[] arrayOfFile = getExtDirs();
      try {
        return( (ExtClassLoader) AccessController.doPrivileged(new PrivilegedExceptionAction(arrayOfFile) {
          public Launcher.ExtClassLoader run() throws IOException {
            int i = this.val$dirs.length;
            for( int j = 0; j < i; ++j ) {
              MetaIndex.registerDirectory(this.val$dirs[j]);
            }
            return new Launcher.ExtClassLoader(this.val$dirs);
          }
        }) );
      }
      catch(PrivilegedActionException localPrivilegedActionException) {
        throw( (IOException) localPrivilegedActionException.getException() );
      }
    }

getExtDirs找到系統(tǒng)配置System.getProperty("java.ext.dirs")中的路徑下的所有文件,
讓類加載器加載這些文件。

    public ExtClassLoader( File[] paramArrayOfFile ) throws IOException {
      super(getExtURLs(paramArrayOfFile), null, Launcher.factory);
      SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this);
    }

ExtClassLoader初始化時將parent設(shè)置為null。

    public static ClassLoader getAppClassLoader( ClassLoader paramClassLoader ) throws IOException {
      String str = System.getProperty("java.class.path");
      File[] arrayOfFile = ( str == null ) ? new File[0] : Launcher.access$200(str);

      return( (ClassLoader) AccessController.doPrivileged(new PrivilegedAction(str, arrayOfFile, paramClassLoader) {
        public Launcher.AppClassLoader run() {
          URL[] arrayOfURL = ( this.val$s == null ) ? new URL[0] : Launcher.access$300(this.val$path);

          return new Launcher.AppClassLoader(arrayOfURL, this.val$extcl);
        }
      }) );
    }

    AppClassLoader( URL[] paramArrayOfURL, ClassLoader paramClassLoader ) {
      super(paramArrayOfURL, paramClassLoader, Launcher.factory);
      this.ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this);
      this.ucp.initLookupCache(this);
    }

getAppClassLoader從System.getProperty("java.class.path")處獲取要加載的文件,并在初始化時將類加載器的parent設(shè)置為ExtClassLoader。

類加載過程

    protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            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 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.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

該函數(shù)邏輯為:

    if 該類已經(jīng)被當前加載器加載過
        return;
    else if 當前類加載器存在parent加載器
        嘗試是否parent加載器加載
    else if 不存在parent加載器器
        嘗試使用BootstrapClassLoader加載器加載

    if 父加載器無法加載該類
        使用當前加載器加載。

因為AppClassLoader的parent為ExtClassLoader,而為ExtClassLoader的parent為null。依據(jù)上面的類加載過程,整個類的加載過程為

類加載過程

這種類加載的模式即為雙親委托模式,簡單一句話描述就是:
類優(yōu)先讓父加載器加載,父加載器無法加載后才會自己加載。

委托機制的意義
防止內(nèi)存中出現(xiàn)多份同樣的字節(jié)碼

比如兩個類A和類B都要加載System類:
如果不用委托而是自己加載自己的,那么加載器A就會加載一份System字節(jié)碼,然后加載器B又會加載一份System字節(jié)碼,這樣內(nèi)存中就出現(xiàn)了兩份System字節(jié)碼。
如果使用委托機制,會遞歸的向父類查找,也就是首選用Bootstrap嘗試加載,如果找不到再向下。這里的System就能在Bootstrap中找到然后加載,如果此時類B也要加載System,也從Bootstrap開始,此時Bootstrap發(fā)現(xiàn)已經(jīng)加載過了System那么直接返回內(nèi)存中的System即可而不需要重新加載,這樣內(nèi)存中就只有一份System的字節(jié)碼了。

當Java虛擬機要加載一個類時,到底派出哪個類加載器去加載呢?

  • 首先當前線程的類加載器去加載線程中的第一個類(假設(shè)為類A)。
    注:當前線程的類加載器可以通過Thread類的getContextClassLoader()獲得,也可以通過setContextClassLoader()自己設(shè)置類加載器。
  • 如果類A中引用了類B,Java虛擬機將使用加載類A的類加載器去加載類B。
  • 還可以直接調(diào)用ClassLoader.loadClass()方法來指定某個類加載器去加載某個類。

違背雙親委派模式
當然雙親委派模式并不是強制要求,在有些情況下會打破雙親委派模式。
如Tomcat加載類過程。

Tomcat類加載體系

commonLoader:類庫可被Tomcat和所有的Web應用程序共同使用。
catalinaLoader:類庫可被Tomcat使用,對所有的Web應用程序都不可見。
sharedLoader:類庫可被所有的Web應用程序共同使用,但對Tomcat自己不可見。
webappclassLoader:每個Web應用程序單獨使用,對其他web應用不可見。

如果有10個Web應用程序都是用Spring來進行組織和管理的話,可以把Spring放到Common或Shared目錄下讓這些程序共享。Spring要對用戶程序的類進行管理,自然要能訪問到用戶程序的類,而用戶的程序顯然是放在/WebApp/WEB-INF目錄中的,那么被CommonClassLoader或SharedClassLoader加載的Spring如何訪問并不在其加載范圍內(nèi)的用戶程序呢?

因為Spring的jar包在common或shared目錄中,所以Spring的類加載器為commonLoader或者sharedLoader。而Spring中的注入的bean是在放在/WebApp/WEB-INF目錄中的,根據(jù) 如果類A中引用了類B,Java虛擬機將使用加載類A的類加載器去加載類B,在Spring中引用web應用的類時,需要commonLoader或者sharedLoader加載去加載,而commonLoader或sharedLoader是無法加載到/WebApp/WEB-INF下的類的,這時候就需要commonLoader或者sharedLoader加載器來調(diào)用子加載器webappclassLoader來加載/WebApp/WEB-INF目錄下的類,在實現(xiàn)時是使用線程上下文類加載器,可以實現(xiàn)父加載器對子加載器的逆向訪問。

熱部署

重新加載
ClassLoader中重要的方法
loadClass
ClassLoader.loadClass(...) 是ClassLoader的入口點。當一個類沒有指明用什么加載器加載的時候,JVM默認采用AppClassLoader加載器加載沒有加載過的class,調(diào)用的方法的入口就是loadClass(...)。如果一個class被自定義的ClassLoader加載,那么JVM也會調(diào)用這個自定義的ClassLoader.loadClass(...)方法來加載class內(nèi)部引用的一些別的class文件。重載這個方法,能實現(xiàn)自定義加載class的方式,拋棄雙親委托機制,但是即使不采用雙親委托機制,比如java.lang包中的相關(guān)類還是不能自定義一個同名的類來代替,主要因為JVM解析、驗證class的時候,會進行相關(guān)判斷。

defineClass
系統(tǒng)自帶的ClassLoader,默認加載程序的是AppClassLoader,ClassLoader加載一個class,最終調(diào)用的是defineClass(...)方法,這時候就在想是否可以重復調(diào)用defineClass(...)方法加載同一個類(或者修改過),最后發(fā)現(xiàn)調(diào)用多次的話會有相關(guān)錯誤:
...
java.lang.LinkageError
attempted duplicate class definition
...
所以一個class被一個ClassLoader實例加載過的話,就不能再被這個ClassLoader實例再次加載(這里的加載指的是,調(diào)用了defileClass(...)放方法,重新加載字節(jié)碼、解析、驗證。)。而系統(tǒng)默認的AppClassLoader加載器,他們內(nèi)部會緩存加載過的class,重新加載的話,就直接取緩存。所與對于熱加載的話,只能重新創(chuàng)建一個ClassLoader,然后再去加載已經(jīng)被加載過的class文件。

class卸載
在Java中class也是可以unload。JVM中class和Meta信息存放在PermGen space區(qū)域。如果加載的class文件很多,那么可能導致PermGen space區(qū)域空間溢出。引起:java.lang.OutOfMemoryErrorPermGen space. 對于有些Class我們可能只需要使用一次,就不再需要了,也可能我們修改了class文件,我們需要重新加載 newclass,那么oldclass就不再需要了。那么JVM怎么樣才能卸載Class呢。

JVM中的Class只有滿足以下三個條件,才能被GC回收,也就是該Class被卸載(unload):

  • 該類所有的實例都已經(jīng)被GC。
  • 加載該類的ClassLoader實例已經(jīng)被GC。
  • 該類的java.lang.Class對象沒有在任何地方被引用。

GC的時機我們是不可控的,那么同樣的我們對于Class的卸載也是不可控的。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關(guān)閱讀更多精彩內(nèi)容

  • 1. Java基礎(chǔ)部分 基礎(chǔ)部分的順序:基本語法,類相關(guān)的語法,內(nèi)部類的語法,繼承相關(guān)的語法,異常的語法,線程的語...
    子非魚_t_閱讀 34,706評論 18 399
  • ClassLoader翻譯過來就是類加載器,普通的java開發(fā)者其實用到的不多,但對于某些框架開發(fā)者來說卻非常常見...
    時待吾閱讀 1,167評論 0 1
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,568評論 19 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 47,273評論 6 342
  • 1. 沒有。比較駁雜,沒有很集中的。 打算讀古文時也多了解一些上古神話。 2. 海子。 在寫作中深刻交融農(nóng)耕文明與...
    _xun閱讀 744評論 2 0

友情鏈接更多精彩內(nèi)容