Tomcat类加载器

1JVM类加载机制
 
JVM的ClassLoader通过Parent属性定义父子关系,可以形成树状结构。其中引导类、扩展类、系统类三个加载器是JVM内置的。
它们的作用分别是:
1)引导类加载器:使用native代码实现,在rt.jar等包中搜索运行JVM所需的类,例如java.lang等包下的类。
2)扩展类加载器:负责载入标准扩展目录中的类,例如Sun的JVM的扩展目录是/jdk/jre/lib/ext。
3)系统类加载器:默认的类加载器,搜索环境变量CLASSPATH中指明的路径。
类加载器树状组织结构示意图 
 
2双亲委派模型
 
既然类加载器是树形结构,那加载类时就需要定义类到底由当前加载器还是父加载器去搜索加载。
JVM加载模型的工作过程是:
     如果一个类加载器收到了类加载的请求,它不会自己去尝试加载这个类,而是把这个请求委派给父类加载器去完成。
     每一个层次的类加载器都是如此,因此所有的加载请求最终都应该传送到顶层的引导类加载器中。
     只有父类加载无法完成这个请求时,子类加载器才会尝试自己去加载。
 
 
为什么要让父类加载器优先去加载呢?试想如果子类加载器先加载,那么我们可以写一些与java.lang包中基础类同名的类,
然后再定义一个子类加载器,这样整个应用使用的基础类就都变成我们自己定义的类了。这样就有很大的安全隐患!
所以自己编写类加载器时,如果没有特殊原因,一定要遵守类加载的双亲委派模型。
 
3Tomcat类加载器
 
Tomcat基本遵守了JVM的委派模型,但也在自定义的类加载器中做了细微的调整,以适应Tomcat自身的要求。
下面是Tomcat类加载器WebappClassLoader的核心方法loadClass()的源码。
它覆盖了父类URLClassLoader中的方法,改变了默认的类加载顺序。
 
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
    public synchronized Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException {
 
        Class clazz = null;
 
        // (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
        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
        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) {
            ;
        }
 
        // (3) Delegate to parent unconditionally
        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);
    }
 
具体分析一下:首先findLoadedClass0()和findLoadedClass()分别从本地和父类加载器的缓存中查找当前要加载的类是否已经加载过了。
之后为了避免上面提到的安全问题,Tomcat类加载器会将加载请求委派给系统类加载器。接下来根据delegate变量的设置,决定是先由自己加载,
还是先由父类加载器去加载。
 
这里介绍一下背景,上面的WebappClassLoader是对应一个Web应用的类加载器,其父亲是Tomcat的lib的加载器。所以delegate变量的值,
决定了Tomcat的类加载顺序。
 
4Tomcat6的加载顺序
 
所以在Tomcat 6中默认情况下,不是完全按照先Tomcat的lib再Web应用的lib这种顺序去加载类。
Jar包的加载顺序是:
1)JRE中的Java基础包
2)Web应用WEB-INF\lib下的包
3)Tomcat\lib下的包
 
如果想要在Web应用间共享一些Jar包,则不仅需要将公共包放在Tomcat的lib下,还要删掉Web应用lib中的包,
否则Tomcat启动时还是会优先加载Web应用lib下的包的。
 
ps:题外话,如果想要自己指定一个Tomcat\lib和Web应用lib之外的ClassPath,除了修改Tomcat启动脚本外,
可以为不同Web应用的Context指定一个VirtualWebappLoader,但源码注释中写到不推荐在生产环境中使用。
 
参考文章
 
《深入理解Java虚拟机》
 
 
 
------------------------------------------------------------------------------------------------------------------------------------------
 
 

如下是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的内存泄露的情况
posted @ 2015-09-11 14:19  人在囧途-博客  阅读(691)  评论(0编辑  收藏  举报