java类加载器-Tomcat类加载器

  在上文中,已经介绍了系统类加载器以及类加载器的相关机制,还自定制类加载器的方式。接下来就以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类的类初始化方法:

  

1
2
3
4
5
6
7
8
9
10
11
12
13
14
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方法:

1
2
3
4
5
6
7
8
9
10
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都是指向同一个类加载器实例,看代码如下:(限于篇幅只贴部分代码)

1
2
3
4
5
6
private ClassLoader createClassLoader(String name, ClassLoader parent)
       throws Exception {
 
       String value = CatalinaProperties.getProperty(name + ".loader");
       if ((value == null) || (value.equals("")))
           return parent;

而他们指向那个类加载器类的实例呢?跟踪到最后我们发现如下代码:

1
2
3
4
5
6
StandardClassLoader classLoader = null;
       if (parent == null)
           classLoader = new StandardClassLoader(array);
       else
           classLoader = new StandardClassLoader(array, parent);
       return (classLoader);

就是StandardClassLoader的实例,下文再对StandardClassLoader进行源码讲解。

  接下来再看看webappclassloader的创建过程,webappclassLoader是在WebappLoader类中的createClassLoader方法中通过反射实例化的。下边是源代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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的这个方法:

1
2
3
4
5
6
7
8
9
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的源码实现:

1
2
3
4
5
6
7
8
9
10
11
12
13
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方法。这个类有三千多行这里就不将代码全部贴出来了。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
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);
    }

  

 

posted @   永志  阅读(2509)  评论(0编辑  收藏  举报
编辑推荐:
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
阅读排行:
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· DeepSeek如何颠覆传统软件测试?测试工程师会被淘汰吗?
点击右上角即可分享
微信分享提示