[Tomcat源碼系列] Tomcat 類載入器結構

來源:互聯網
上載者:User

一、從類載入器(ClassLoader)結構說起
1.基本介紹(此部分可參見<<Core Java 2 Volume II>> Chapter9. Security)
      顧名思義,類載入器是用於載入Java的類定義資訊(.class)。需要注意的是類載入器僅在需要的才載入類定義資訊,參見<<Core Java 2 Volume II>> Chapter9. Security關於ClassLoader的說明如下

      Note that the virtual machine loads only those class files that are needed for the execution of a program. For example, suppose program execution starts with MyProgram.class. Here are the steps that the virtual machine carries out.
  • The virtual machine has a mechanism for loading class files, for example, by reading the files from disk or by requesting them from the Web; it uses this mechanism to load the contents of the MyProgram class file.
  • If the MyProgram class has instance variables or superclasses of another class type, these class files are loaded as well. (The process of loading all the classes that a given class depends on is called resolving the class.)
  • The virtual machine then executes the main method in MyProgram (which is static, so no instance of a class needs to be created).
  • If the main method or a method that main calls requires additional classes, these are loaded next.

 

JVM的類載入機制使用多個ClassLoader來完成類載入的功能,譬如JVM至少會包含如下三個類載入器:

  • The bootstrap class loader:啟動類載入器,是JVM的組成部分,使用C語言實現。載入JVM基礎Class(譬如rt.jar)。實際沒有Bootstrap類載入器的執行個體,譬如String.class.getClassLoader()返回null
  • The extension class loader:擴充類載入器,JVM標準擴充類載入器,載入位於{jre path}/lib/ext目錄下類
  • The system class loader:系統載入器,載入在classpath路徑下定義的class

我們可以通過 obj.getClass().getClassLoader()來獲得對象相應的類定義是由哪個ClassLoader載入的
2.對象建立和ClassLoader
     JVM提供了如下三種建立對象的方式

  • new:通過new操作建立對象,那麼相應對象的類定義由建立操作所在的類的類載入器載入
  • Class.forName("...").newInstance:類定義的載入器與new相同
  • xxxClassLoader.loadClass("...").newInstance:類定義的載入器為xxxClassLoader

      需要注意的是,JVM識別類定義之間是否一樣,除了檢查類全名(譬如xxx.MyClass)是否一樣,還檢查其相應的ClassLoader是否一樣。譬如如下操作會拋出ClassCastException

Object obj = xxxClassLoader.loadClass("xxx.MyClass").newInstance(); //此處xxxClassLoader的parent不是當前類的載入器xxx.MyClass xxx = (xxx.MyClass) obj;//ClassLoader不一樣,因此JVM認為是類型是不一樣的

3.ClassLoader程式碼分析(java.lang.ClassLoader)
我們從入口loadClass開始

protected synchronized Class<?> loadClass(String name, boolean resolve)    throws ClassNotFoundException    {    //檢查是是否已經載入過    Class c = findLoadedClass(name);    if (c == null) {        try {        if (parent != null) {            //代理給parent去載入類            c = parent.loadClass(name, false);        } else {            //由Bootstrap去載入類            c = findBootstrapClass0(name);        }        } catch (ClassNotFoundException e) {            //parent和Bootstrap都無法載入,則由自訂的方式去載入類            //通過擴充該方法來實現自訂的類載入器            c = findClass(name);        }    }//這是標準的也是JVM推薦的類載入方式,先由parent,然後由Bootstrap,最後是自訂的類,然而Servlet規範定義的是與此相悖的,後面我們再看    if (resolve) {        resolveClass(c);    }    return c;}

自訂的類載入器主要會有兩個過程:獲得類定義的byte串、解析byte串到class的結構,第一個步驟是我們應該處理的,而第二個步驟是 JVM直接提供的,也就是ClassLoader的defineClass方法,有興趣可以看看 java.lang.ClassLoader.defineClass方法

二、Tomat類載入器結構
1.如下是Tomcat6的類載入器結果圖

寫道      [BootStrapClassLoader](實際沒有這個類)
                      |
     ExtensionClassLoader(對於Sun JVM,是sun.misc.Launcher$ExtClassLoader)
                      |
     SystemClassLoader(對於Sun JVM,是sun.misc.Launcher$AppClassLoader)
                      |
     CommonClassLoader(對於Tomcat 6,是org.apache.catalina.loader.StandardClassLoader)
               /               \
CatalinaClassLoader           SharedClassLoader
                                             |
                          org.apache.catalina.loader.WebappClassLoader
                                             |
                              org.apache.jasper.servlet.JasperLoader
  • CommonClassLoader:載入的類目錄通過{tomcat}/conf/catalina.properties中的 common.loader指定,以SystemClassLoader為parent(目前預設定義是 common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar)
  • CatalinaClassLoader   :載入的類目錄通過{tomcat}/conf/catalina.properties中server.loader指定,以 CommonClassLoader為parent,如果server.loader配置為空白,則ServerClassLoader 與CommonClassLoader是同一個(預設server.loader配置為空白)
  • SharedClassLoader:載入的類目錄通過{tomcat}/conf/catalina.properties中 share.loader指定,以CommonClassLoader為parent,如果server.loader配置為空白,則 CatalinaClassLoader 與CommonClassLoader是同一個(預設share.loader配置為空白)
  • WebappClassLoader:每個Context一個WebappClassLoader執行個體,負責載入context的/WEB- INF/lib和/WEB-INF/classes目錄,context間的隔離就是通過不同的WebappClassLoader來做到的。由於類定義一旦載入就不可改變,因此要實現tomcat的context的reload功能,實際上是通過建立一個新的WebappClassLoader來做的,因此reload的做法實際上代價是很高昂的,需要注意的是,JVM記憶體的Perm區是只吃不拉的,因此拋棄掉的WebappClassLoader載入的類並不會被JVM釋放,因此tomcat的reload功能如果應用定義的類比較多的話,reload幾次就OutOfPermSpace異常了。(關於JVM的記憶體管理,可以參見之前的文章
    ,後續對這一塊重新做總結)
  • JasperLoader:每個JSP一個JasperLoader執行個體,與WebappClassLoader做法類似,JSP支援修改生效是通過丟棄舊的JasperLoader,建一個新的JasperLoader來做到的,同樣的,存在輕微的PermSpace的記憶體泄露的情況

2.WebappClassLoader詳解
       我們來看看WebappClassLoader具體是如何?的,如上,loadClass方法是我們的重點(有時候我們的類會使用 Class.getResourceAsStream或者ClassLoader.getResourceAsStream,這種搜尋資源的方式會與 loadClass的機制類似,因此這裡不重複說明)。

Java代碼:
public Class loadClass(String name, boolean resolve)        throws ClassNotFoundException {        if (log.isDebugEnabled())            log.debug("loadClass(" + name + ", " + resolve + ")");        Class clazz = null;        // Log access to stopped classloader        if (!started) {            try {                throw new IllegalStateException();            } catch (IllegalStateException e) {                log.info(sm.getString("webappClassLoader.stopped", name), e);            }        }        // (0) 檢查WebappClassLoader之前是否已經load過這個資源clazz = findLoadedClass0(name);        if (clazz != null) {            if (log.isDebugEnabled())                log.debug("  Returning class from cache");            if (resolve)                resolveClass(clazz);            return (clazz);        }        // (0.1) 檢查ClassLoader之前是否已經load過        clazz = findLoadedClass(name);        if (clazz != null) {            if (log.isDebugEnabled())                log.debug("  Returning class from cache");            if (resolve)                resolveClass(clazz);            return (clazz);        }        // (0.2) 先檢查系統ClassLoader,因此WEB-INF/lib和WEB-INF/classes或{tomcat}/libs下的類定義不能覆蓋JVM 底層能夠尋找到的定義(譬如不能通過定義java.lang.Integer替代底層的實現        try {            clazz = system.loadClass(name);            if (clazz != null) {                if (resolve)                    resolveClass(clazz);                return (clazz);            }        } catch (ClassNotFoundException e) {            // Ignore        }        // (0.5) Permission to access this class when using a SecurityManager        if (securityManager != null) {            int i = name.lastIndexOf('.');            if (i >= 0) {                try {                    securityManager.checkPackageAccess(name.substring(0,i));                } catch (SecurityException se) {                    String error = "Security Violation, attempt to use " +                        "Restricted Class: " + name;                    log.info(error, se);                    throw new ClassNotFoundException(error, se);                }            }        }        //這是一個很奇怪的定義,JVM的ClassLoader建議先由parent去load,load不到自己再去load(見如上 ClassLoader部分),而Servelet規範的建議則恰好相反,Tomcat的實現則做個折中,由使用者去決定(context的 delegate定義),預設使用Servlet規範的建議,即delegate=false        boolean delegateLoad = delegate || filter(name);        // (1) 先由parent去嘗試載入,此處的parent是SharedClassLoader,見如上說明,如上說明,除非設定了delegate,否則這裡不執行        if (delegateLoad) {            if (log.isDebugEnabled())                log.debug("  Delegating to parent classloader1 " + parent);            ClassLoader loader = parent;             //此處parent是否為空白取決於context 的privileged屬性配置,預設privileged=true,即parent為SharedClassLoader            if (loader == null)                loader = system;            try {                clazz = loader.loadClass(name);                if (clazz != null) {                    if (log.isDebugEnabled())                        log.debug("  Loading class from parent");                    if (resolve)                        resolveClass(clazz);                    return (clazz);                }            } catch (ClassNotFoundException e) {                ;            }        }        // (2) 到WEB-INF/lib和WEB-INF/classes目錄去搜尋,細節部分可以再看一下findClass,會發現預設是先搜尋WEB-INF/classes後搜尋WEB-INF/lib        if (log.isDebugEnabled())            log.debug("  Searching local repositories");        try {            clazz = findClass(name);            if (clazz != null) {                if (log.isDebugEnabled())                    log.debug("  Loading class from local repository");                if (resolve)                    resolveClass(clazz);                return (clazz);            }        } catch (ClassNotFoundException e) {            ;        }        // (3) 由parent再去嘗試載入一下        if (!delegateLoad) {            if (log.isDebugEnabled())                log.debug("  Delegating to parent classloader at end: " + parent);            ClassLoader loader = parent;            if (loader == null)                loader = system;            try {                clazz = loader.loadClass(name);                if (clazz != null) {                    if (log.isDebugEnabled())                        log.debug("  Loading class from parent");                    if (resolve)                        resolveClass(clazz);                    return (clazz);                }            } catch (ClassNotFoundException e) {                ;            }        }        throw new ClassNotFoundException(name);    }

 

原文連結地址:http://ayufox.iteye.com/blog/631190

 

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.