在上文中,已經介紹了 系統類加載器 以及類加載器的相關機制,還自定制類加載器的方式。接下來就以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元等您想捐的金額吧,狠狠點擊下面給點支持吧,站長非常感激您!手機微信長按不能支付解決辦法:請將微信支付二維碼保存到相冊,切換到微信,然后點擊微信右上角掃一掃功能,選擇支付二維碼完成支付。
【本文對您有幫助就好】元

