一、從類載入器(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