在上文中,已經介紹了 系統類加載器 以及類加載器的相關機制,還自定制類加載器的方式。接下來就以tomcat6為例看看tomat是如何使用自定制類加載器的。(本介紹是基于tomcat6.0.41,不同版本可能存在差異!)
網上所描述的tomcat類加載器
在網上搜一下“tomcat類加載器”會發現有大量的文章,在此我偷個懶,^_^把網上對tomcat類加載器的描述重說一下吧。
- 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異常了。
- JasperLoader:每個JSP一個JasperLoader實例,與WebappClassLoader做法類似,JSP支持修改生效是通過丟棄舊的JasperLoader,建一個新的JasperLoader來做到的,同樣的,存在輕微的PermSpace的內存泄露的情況
以上對個個classloader的作用做了介紹,但請讀者不要搞混淆了,上邊說的個個類加載器只是類加載器的名字,不是類加載類的名字。上邊的圖是看到網上資料的說明繪制的,但是與實際源碼中的結構還是差異挺大的。(沒有研究是不是因為tomcat的版本所致)。下面就詳細介紹下tomcat源碼中類加載器的組織結構。
tomcat源碼中類加載器的結構分析
首先要說明是tomcat默認配置下的情況。那接下來看看tomcat啟動時的類初始化情況,這是BootStrap類的類初始化方法:
??
private void initClassLoaders() { try { commonLoader = createClassLoader("common", null); if( commonLoader == null ) { // no config file, default to this loader - we might be in a 'single' env. commonLoader=this.getClass().getClassLoader(); } catalinaLoader = createClassLoader("server", commonLoader); sharedLoader = createClassLoader("shared", commonLoader); } catch (Throwable t) { log.error("Class loader creation threw exception", t); System.exit(1); } }
?
可以看到,在創建commonLoader時傳的父類加載器是null。跟蹤下去會發現commonLoader的父類加載器確實是null。有朋友可能想,tomcat在啟動時肯定也要依賴jdk核心庫,parent是null那怎么委托給parent去加載核心庫的類了啊。這里大家不要忘了ClassLoader類的loadClass方法:
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 }
通過以上initClassLoaders方法我們也能看到catalinaLoader和sharedLoader的父類加載器都是commonLoader,跟上邊圖的類加載器結構符合。但是commonLoader、catalinaLoader和sharedLoader的創建都是依賴tomcat安裝目錄下conf/catalina.properties的配置。默認情況配置是:
- common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
- server.loader=
- shared.loader=
由于server.loader和shared.loader的配置為空,所以其實commonLoader、catalinaLoader和sharedLoader都是指向同一個類加載器實例,看代碼如下:(限于篇幅只貼部分代碼)
private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception { String value = CatalinaProperties.getProperty(name + ".loader"); if ((value == null) || (value.equals(""))) return parent;
而他們指向那個類加載器類的實例呢?跟蹤到最后我們發現如下代碼:
StandardClassLoader classLoader = null; if (parent == null) classLoader = new StandardClassLoader(array); else classLoader = new StandardClassLoader(array, parent); return (classLoader);
就是StandardClassLoader的實例,下文再對StandardClassLoader進行源碼講解。
接下來再看看webappclassloader的創建過程,webappclassLoader是在WebappLoader類中的createClassLoader方法中通過反射實例化的。下邊是源代碼:
private WebappClassLoader createClassLoader() throws Exception { Class clazz = Class.forName(loaderClass);//loaderClass="org.apache.catalina.loader.WebappClassLoader" WebappClassLoader classLoader = null; if (parentClassLoader == null) { parentClassLoader = container.getParentClassLoader(); } Class[] argTypes = { ClassLoader.class }; Object[] args = { parentClassLoader }; Constructor constr = clazz.getConstructor(argTypes); classLoader = (WebappClassLoader) constr.newInstance(args); return classLoader; }
可以看到他的parent是通過調用container.getParentlassLoader()獲得的(如果對tomcat的結構不熟悉,請看這篇 文章 )跟蹤到最后我們發現它調用了ContainerBase的這個方法:
public ClassLoader getParentClassLoader() { if (parentClassLoader != null) return (parentClassLoader); if (parent != null) { return (parent.getParentClassLoader()); } return (ClassLoader.getSystemClassLoader()); }
通過默認配置下debug可以知道最后是返回的systemclassloader,也就是說WebappClassLoader的父類加載器是systemclassloader也就是 上篇文章 說的App ClassLoader。
(由于JasperLoader本人還沒有做分析,先不進行講解了)
tomcat類加載器的實現方式分析
上文說到了commonLoader、catalinaLoader和sharedLoader都是指向StandardClassLoader的實例,來先看一看StandardClassLoader的源碼實現:
public class StandardClassLoader extends URLClassLoader implements StandardClassLoaderMBean { public StandardClassLoader(URL repositories[]) { super(repositories); } public StandardClassLoader(URL repositories[], ClassLoader parent) { super(repositories, parent); } }
有沒有感到你的意外啊,對的就是這么簡單,這跟我 上篇文章 說的最簡單的實現方式一樣。(上篇文章做了解讀,這里不再做說明了)
我們再來看看webappclassLoader,他的實現類就是org.apache.catalina.loader.WebappClassLoader,此類加載器也是繼承自URLClassLoader,但是它覆蓋了loadClass方法和findClass方法。這個類有三千多行這里就不將代碼全部貼出來了。
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的類加載機制建議先由parent去load,load不到自己再去load(見上篇文章),而Servelet規范的建議則恰好相反,Tomcat的實現則做個折中,由用戶去決定(context的 delegate定義),默認使用Servlet規范的建議,即delegate=false boolean delegateLoad = delegate || filter(name); // (1) 先由parent去嘗試加載,如上說明,除非設置了delegate,否則這里不執行 if (delegateLoad) { if (log.isDebugEnabled()) log.debug(" Delegating to parent classloader1 " + 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) { ; } } // (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); }
?
更多文章、技術交流、商務合作、聯系博主
微信掃碼或搜索:z360901061

微信掃一掃加我為好友
QQ號聯系: 360901061
您的支持是博主寫作最大的動力,如果您喜歡我的文章,感覺我的文章對您有幫助,請用微信掃描下面二維碼支持博主2元、5元、10元、20元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元
