Tomcat 原始碼分析之ClassLoader
此系列文章皆為Tomcat 7.0代碼程式碼分析。
1. ClassLoader基礎知識1.1. Parent-Child委託模型
我們知道Java系統中,類載入器的預設載入方式是採用Parent-Child委託方式載入類的,即就是說,先嘗試使用父類載入器載入類,如果沒有找到,才自己載入該類,可以看到,這是一個遞迴的載入過程,核心代碼大致如下:
Java代碼
- 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) {
- ……
- 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.
- …
- c = findClass(name);
- ……
- }
- }
- ……
- return c;
- }
- }
很多ClassLoader的繼承類都預設了這種載入方式,其中用途比較廣泛的有URLClassLoader。1.2. 類載入器運行時模型:
當一個JVM啟動時,至少有三個ClassLoader會被啟動:
1. Bootstrap 類載入器
載入Java核心包,它們被置放在(<JAVA_HOME>/lib目錄下,這部分是JVM的一部分,往往使用native code完成
2. Extensions類載入器
載入Java擴充包,即位於<JAVA_HOME>/lib/ext下的包,這裡要注意的是,有些JVM的這個載入器和Bootstrap類載入器是同一個載入器,而Sun是把二者分開的,其實作類別為sun.misc.Launcher$ExtClassLoader。
3. System類載入器
這個類載入器載入CLASSPATH下的類,Sun的 預設實現是sun.misc.Launcher$ExtClassLoader。
這三者之間的父子關係是:Bootstrap類載入器是Extensions類載入器的父類載入器,而Extensions類載入器是System類載入器的父類載入器。
2. Tomcat Classloader模型
Tomcat 7.0的ClassLoader載入層次模型如所示:
這個模型和之前Tomcat 5.5之前有些不同,因為之前的除過Common類載入器之外,還有Catalina類載入器和Shared類載入器,這個只能導致更多的配置和概念,已經不再使用了,雖然還可以進行配置。
1. Common類載入器:載入$CATALINA_HOME/lib和$CATALINA_BASE/lib下的class檔案和jar包以及旗下的資源檔,這些檔案將會被所有Web應用共有,也會被Tomcat運行時用到。其配置在catalina.properties下,如果沒有配置,則預設使用System類載入器,配置樣本:common.loader=${catalina.home}/lib,${catalina.home}/lib/*.jar2. App類載入器:Web 應用程式的類載入器,Web應用下的資源檔,class檔案盒jar包,按照Java Web規範的標準,它們位於Web應用的/WEB-INF/classes和/WEB-INF/lib檔案夾下。3. 類載入器的實現
Tomcat類載入器其實有三個類實現:StandardClassLoader,WebappClassLoader和JasperLoader,這三個類載入器都是URLClassLoader的子類。不同的是,StandardClassLoader並沒有客戶化URLClassLoader方法,即,它也是採用委託的方式載入類的。而其他都覆寫了loadClass()方法。
1. StandardClassLoader類載入器會建立Common類載入器的執行個體。
2. WebappClassLoader為每個應用建立類載入器執行個體,對於載入Web應用的類時,我們並非推薦使用父子委託模型,但是必須保證以java.*和javax.*開頭的包必須由System類載入器載入,即最終由Bootstrap載入器載入。
核心代碼如下:
Java代碼
- public Class loadClass(String name, boolean resolve)
- throws ClassNotFoundException {
-
- ……
-
- // (0) Check our previously loaded local class cache
- clazz = findLoadedClass0(name);
- if (clazz != null) {
- if (log.isDebugEnabled())
- log.debug(" Returning class from cache");
- if (resolve)
- resolveClass(clazz);
- return (clazz);
- }
-
- // (0.1) Check our previously loaded class cache
- clazz = findLoadedClass(name);
- if (clazz != null) {
- if (log.isDebugEnabled())
- log.debug(" Returning class from cache");
- if (resolve)
- resolveClass(clazz);
- return (clazz);
- }
-
- // (0.2) Try loading the class with the system class loader, to prevent
- // the webapp from overriding J2SE classes
- //保證以java.*和javax.*開頭的包必須由System類載入器載入
- try {
- clazz = system.loadClass(name);
- if (clazz != null) {
- if (resolve)
- resolveClass(clazz);
- return (clazz);
- }
- } catch (ClassNotFoundException e) {
- // Ignore
- }
-
- ……
-
- boolean delegateLoad = delegate || filter(name);
-
- // (1) Delegate to our parent if requested
- //Mbeans載入時,是否採用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) Search local repositories
- //先本地載入,不委託父載入器載入
- 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) {
- ;
- }
-
- // 最後使用父載入器載入
- 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);
- }
可以看到,並非簡單的父子委託方式載入類
3. JasperLoader是為了載入jsp編譯成的servlet而建立的類載入器,它覆寫了loadClass()方法,除過載入org.apache.jsp.*的檔案外,其他的均使用父載入器載入。
核心代碼如下:
Java代碼
- public Class loadClass(final String name, boolean resolve)
- throws ClassNotFoundException {
-
- Class clazz = null;
-
- // (0) Check our previously loaded class cache
- clazz = findLoadedClass(name);
- if (clazz != null) {
- if (resolve)
- resolveClass(clazz);
- return (clazz);
- }
-
- // (.5) Permission to access this class when using a SecurityManager
- ……
-
- if( !name.startsWith(Constants.JSP_PACKAGE_NAME) ) {
- // Class is not in org.apache.jsp, therefore, have our
- // parent load it
- clazz = parent.loadClass(name);
- if( resolve )
- resolveClass(clazz);
- return clazz;
- }
- //所有的以org.apache.jsp.*開頭的檔案都由該類載入器載入。
- return findClass(name);
- }
今天就講到這裡了。