Tomcat类加载机制

双亲委派机制

 protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            Class c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                try {
                    if (parent != null) { 
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                }
                if (c == null) {
                    long t1 = System.nanoTime();
                    c = findClass(name);
                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

为何Tomcat需要违背双亲委派机制

  • Tomcat可能需要部署多个应用程序,不同的应用程序可能会依赖同一个第三方类库的不同版本。
  • 同一个web容器中相同的类库相同的版本可以共享。
  • Tomcat有自己依赖的类库,不能于应用程序的类库混淆。
  • Tomcat需要支持 jsp 修改后不用重启。

Tomcat类加载机制

CommonClassLoader:Tomcat最基本的类加载器,加载路径中的class可以被Tomcat容器本身以及各个Webapp访问。

CatalinaClassLoader:Tomcat容器私有的类加载器,加载路径中的class对于Webapp不可见,供Tomcat使用。

SharedClassLoader:各个Webapp共享的类加载器,加载路径中的class对于所有Webapp可见,但是对于Tomcat容器不可见。

WebAppClassLoader:各个Webapp私有的类加载器,加载路径中的class只对当前Webapp可见;

JasperClassLoader:JSP生成的类对应的加载器

 

Common,Catalina,Shared类加载器是URLClassLoader类的一个实例,在默认的配置中,它们其实都是同一个对象,即commonLoader,结合初始化时的代码(只保留关键代码):

 private void initClassLoaders() {
        commonLoader = createClassLoader("common", null);  // commonLoader的加载路径为common.loader
        if( commonLoader == null ) {
            commonLoader=this.getClass().getClassLoader();
        }
        catalinaLoader = createClassLoader("server", commonLoader); // 加载路径为server.loader,默认为空,父类加载器为commonLoader
        sharedLoader = createClassLoader("shared", commonLoader); // 加载路径为shared.loader,默认为空,父类加载器为commonLoader
    }
 private ClassLoader createClassLoader(String name, ClassLoader parent) throws Exception {
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;      // catalinaLoader与sharedLoader的加载路径均为空,所以直接返回commonLoader对象,默认3者为同一个对象
    }

在上面的代码初始化时很明确是指出了,catalina与shared类加载器的父类加载器为common类加载器,而初始化commonClassLoader时父类加载器设置为null,最终会调到createClassLoader静态方法:

 public static ClassLoader createClassLoader(List<Repository> repositories,
                                                final ClassLoader parent)
        throws Exception {
        .....
        return AccessController.doPrivileged(
                new PrivilegedAction<URLClassLoader>() {
                    @Override
                    public URLClassLoader run() {
                        if (parent == null)
                            return new URLClassLoader(array);  //该构造方法默认获取系统类加载器为父类加载器,即AppClassLoader
                        else
                            return new URLClassLoader(array, parent);
                    }
                });

    }

在createClassLoader中指定参数parent==null时,最终会以系统类加载器(AppClassLoader)作为父类加载器,这解释了为什么commonClassLoader的父类加载器是AppClassLoader.

一个web应用对应着一个StandardContext实例,每个web应用都拥有独立web应用类加载器(WebClassLoader),这个类加载器在StandardContext.startInternal()中被构造了出来:

tomcat的类加载机制是违反了双亲委托原则的,对于一些未加载的非基础类(Object,String等),各个web应用自己的类加载器(WebAppClassLoader)会优先加载,加载不到时再交给commonClassLoader走双亲委托。具体的加载逻辑位于WebAppClassLoaderBase.loadClass()方法中,代码篇幅长,这里以文字描述加载一个类过程:

  1. 先在本地缓存中查找是否已经加载过该类(对于一些已经加载了的类,会被缓存在resourceEntries这个数据结构中),如果已经加载即返回,否则 继续下一步。
  2. 让系统类加载器(AppClassLoader)尝试加载该类,主要是为了防止一些基础类会被web中的类覆盖,如果加载到即返回,返回继续。
  3. 前两步均没加载到目标类,那么web应用的类加载器将自行加载,如果加载到则返回,否则继续下一步。
  4. 最后还是加载不到的话,则委托父类加载器(Common ClassLoader)去加载。

第3第4两个步骤的顺序已经违反了双亲委托机制,除了tomcat之外,JDBC,JNDI,Thread.currentThread().setContextClassLoader();等很多地方都一样是违反了双亲委托。

 

参考文献

https://blog.csdn.net/czmacd/article/details/54017027

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值