首页 > hot资讯 > Tomcat类加载机制

Tomcat类加载机制

更新时间:2020-12-03 17:16 浏览97次 来源:动力节点

说到Tomcat类加载机制,我们不得不提及JVM的类加载,然后Tomcat也是运行在JVM上的。所以,我们先抛砖引玉,一起来看看JVM类加载。

 

JVM类加载采用父类委托机制,当JVM运行过程中,用户需要加载某些类时,用户自己的类加载器,把加载请求传给父加载器,父加载器再传给其父加载器,一直到加载器树的顶层。最顶层的类加载器首先针对其特定的位置加载,如果加载不到就转交给子类。如果一直到底层的类加载都没有加载到,那么就会抛出异常ClassNotFoundException。


回过头来,我们再来看Tomcat类加载,Tomcat类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。

 

下面的简图是Tomcat9版本的官方文档给出的Tomcat的类加载器的图。

 

      Bootstrap

          |

       System

          |

       Common

       /     \

  Webapp1   Webapp2 ..

 

Bootstrap :是Java的最高的加载器,用C语言实现,主要用来加载JVM启动时所需要的核心类,例如$JAVA_HOME/jre/lib/ext路径下的类。

System: 会加载CLASSPATH系统变量所定义路径的所有的类。

Common:会加载Tomcat路径下的lib文件下的所有类。

Webapp1、Webapp2……: 会加载webapp路径下项目中的所有的类。一个项目对应一个WebappClassLoader,这样就实现了应用之间类的隔离了。

 

这3个部分,在上面的Java双亲委派模型图中都有体现。不过可以看到ExtClassLoader没有画出来,可以理解为是跟bootstrap合并了,都是去JAVA_HOME/jre/lib下面加载类。那么Tomcat为什么要自定义类加载器呢?

 

隔离不同应用:部署在同一个Tomcat中的不同应用A和B,例如A用了Spring2.5。B用了Spring3.5,那么这两个应用如果使用的是同一个类加载器,那么Web应用就会因为jar包覆盖而无法启动。

灵活性:Web应用之间的类加载器相互独立,那么就可以根据修改不同的文件重建不同的类加载器替换原来的。从而不影响其他应用。

性能:如果在一个Tomcat部署多个应用,多个应用中都有相同的类库依赖。那么可以把这相同的类库让Common类加载器进行加载。

 

Tomcat自定义了WebAppClassLoader类加载器。打破了双亲委派的机制,即如果收到类加载的请求,会尝试自己去加载,如果找不到再交给父加载器去加载,目的就是为了优先加载Web应用自己定义的类。我们知道ClassLoader默认的loadClass方法是以双亲委派的模型进行加载类的,那么Tomcat既然要打破这个规则,就要重写loadClass方法,我们可以看WebAppClassLoader类中重写的loadClass方法。

@Override

public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {

 

    synchronized (getClassLoadingLock(name)) {

        Class clazz = null;

// 1. 从本地缓存中查找是否加载过此类

        clazz = findLoadedClass0(name);

        if (clazz != null) {

            if (log.isDebugEnabled())

                log.debug("  Returning class from cache");

            if (resolve)

                resolveClass(clazz);

            return clazz;

        }

 

// 2. 从AppClassLoader中查找是否加载过此类

        clazz = findLoadedClass(name);

        if (clazz != null) {

            if (log.isDebugEnabled())

                log.debug("  Returning class from cache");

            if (resolve)

                resolveClass(clazz);

            return clazz;

        }

 

        String resourceName = binaryNameToPath(name, false);

// 3. 尝试用ExtClassLoader 类加载器加载类,防止Web应用覆盖JRE的核心类

        ClassLoader javaseLoader = getJavaseClassLoader();

        boolean tryLoadingFromJavaseLoader;

        try {

            URL url;

            if (securityManager != null) {

                PrivilegedActiondp = new PrivilegedJavaseGetResource(resourceName);

                url = AccessController.doPrivileged(dp);

            } else {

                url = javaseLoader.getResource(resourceName);

            }

            tryLoadingFromJavaseLoader = (url != null);

        } catch (Throwable t) {

            tryLoadingFromJavaseLoader = true;

        }

 

        boolean delegateLoad = delegate || filter(name, true);

 

 // 4. 判断是否设置了delegate属性,如果设置为true那么就按照双亲委派机制加载类

        if (delegateLoad) {

            if (log.isDebugEnabled())

                log.debug("  Delegating to parent classloader1 " + parent);

            try {

                clazz = Class.forName(name, false, parent);

                if (clazz != null) {

                    if (log.isDebugEnabled())

                        log.debug("  Loading class from parent");

                    if (resolve)

                        resolveClass(clazz);

                    return clazz;

                }

            } catch (ClassNotFoundException e) {

                // Ignore

            }

        }

  // 5. 默认是设置delegate是false的,那么就会先用WebAppClassLoader进行加载

        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) {

            // Ignore

        }

 // 6. 如果此时在WebAppClassLoader没找到类,那么就委托给AppClassLoader去加载

        if (!delegateLoad) {

            if (log.isDebugEnabled())

                log.debug("  Delegating to parent classloader at end: " + parent);

            try {

                clazz = Class.forName(name, false, parent);

                if (clazz != null) {

                    if (log.isDebugEnabled())

                        log.debug("  Loading class from parent");

                    if (resolve)

                        resolveClass(clazz);

                    return clazz;

                }

            } catch (ClassNotFoundException e) {

                // Ignore

            }

        }

    }

    throw new ClassNotFoundException(name);

}

 

我们总结起来就是Web应用默认的类加载顺序是(打破了双亲委派规则):

1.先从JVM的BootStrapClassLoader中加载。

2.加载Web应用下/WEB-INF/classes中的类。

3.加载Web应用下/WEB-INF/lib/*.jap中的jar包中的类。

4.加载上面定义的System路径下面的类。

5.加载上面定义的Common路径下面的类。

 

到此为止,Tomcat类加载机制逐渐明朗,Tomcat类加载机制的重点就是打破了双亲委派机制,WebAppClassLoader加载类的时候,绕开 AppClassLoader,直接先使用 ExtClassLoader 来加载类。这不仅保证了基础类不会被同时加载,也

保证了在同一个 Tomcat 下不同 web 之间的 class 是相互隔离的。好了。Tomcat的类加载机制就讲到这里,感兴趣的小伙伴可以去观看本站的Tomcat服务器教程,深入学习Tomcat里的各种技术。


相关文章推荐
HOT资讯 >

热门课程推荐

全部班型支持免费试学

动力节点在线报名表(此信息已加密,请放心填写)

返回顶部