前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >tomcat类加载机制了解一下

tomcat类加载机制了解一下

作者头像
虞大大
发布2020-08-26 17:18:47
2.2K0
发布2020-08-26 17:18:47
举报
文章被收录于专栏:码云大作战

一、tomcat类加载器关系

tomcat类加载器设计结构如上图所示,上面三个Bootstrap加载器、Ext加载器、Application加载器是JVM加载器,下半部分的加载器才是tomcat自定义的加载器。

学习tomcat类加载器之前先回顾下之前写过的jvm类加载器的知识点。

· 类加载机制

类加载机制的本质是将经过虚拟机编译后的.class文件转化为二进制,并将二进制加载成Class对象的加载过程。

因此判断类是否完全相同,需要以下两个条件即:类的全路径名是否相同、加载类的类加载器是否为同一个,这两个条件决定类是否完全相同。

· 双亲委派机制

双亲委派机制的存在,保证类是安全的,他的存在保证了类的加载顺序会优先委派父类加载器进行加载,如果父类加载器没有加载才会有子类进行加载。比如在JVM类加载器的继承关系(从子到父)为Application ClassLoader—>Extension ClassLoader—>Bootstrap ClassLoader。因此当自己在代码内部中编写恶意java代码时,比如HashMap文件,如果路径名相同,则会委派给父类Bootstrap ClassLoader进行加载,发现在Bootstrap加载的lib目录下存在HashMap文件则加载应该由Bootstrap加载的HashMap文件。从而保证自己编写的恶意java代码不会替换应由Bootstrap加载的源文件。

· 打破双亲委派机制

由于虚拟机中的加载规则是按需加载的,即需要用到什么类的时候才会去加载那个类。并且在加载该类时用的是什么加载器,那么加载该类引用的类也需要用到对应的加载器,在java中的SPI机制,加载jdbc时由于Driver类不在rt.jar中因此不能被Bootstrap加载器进行加载,因此使用了线程上下文类加载器委派子类进行加载。所以打破了双亲委派机制,并且在tomcat类加载器中也存在打破双亲委派机制的情况。

二、tomcat类加载器

· Common ClassLoader

Common ClassLoader是tomcat最基本的类加载器,被此加载器加载的类即可以被tomcat所访问,也可以被应用war包中的程序所访问。

· Catalina ClassLoader

Catalina ClassLoader是tomcat私有的类加载器,被此加载器加载的类,只能被tomcat所访问。

· Shared ClassLoader

Shared ClassLoader是各个war包共享的类加载器,被此加载器加载的类,只能被应用war包中的程序所访问。

当tomcat中存在多个war包并同时使用了相同版本的jar包时,为了减少资源的浪费,可以使用该加载器,抽出这些相同版本的jar包,使用Shared ClassLoader加载一次被共享的jar即可,来代替每个war包中都需要加载的过程。

· WebApp ClassLoader

WebApp ClassLoader是多个war包的类加载器,即tomcat中的一个war包由一个WebApp ClassLoader加载。

Shared ClassLoader和WebApp ClassLoader的存在可以让共享的jar包由Shared ClassLoader加载,其余不一样或需要独立部署的由WebApp ClassLoader进行加载,达成共享与隔离的统一。

· JSP ClassLoader

JSP ClassLoader是一个jsp文件的类加载器,即tomcat中存在一个jsp就会new出一个JSP ClassLoader进行jsp文件的加载。

JSP ClassLoader是一个特殊的类加载器,他的特点也保证了jsp文件可以进行热部署,因为当对jsp文件进行修改,重新加载该jsp文件时,又会new出一个JSP ClassLoader来加载jsp文件,替换修改前的jsp文件(此时需要注意的是jsp文件的类的唯一性也变了,类的唯一性由类的全路径名+类加载器决定,此时的类加载器是重新new出来的所以和修改前的加载器是完全不一样的)。而Controller、service等文件的修改前和修改后是由相同的WebApp ClassLoader加载的,因此不能在这种情况下和jsp一样实现实现修改后的热部署。

· 源码中的加载器关系

代码语言:javascript
复制
private void initClassLoaders() {
    try {
        //common加载器,父类为null
        commonLoader = createClassLoader("common", null);
        if (commonLoader == null) {
            commonLoader = this.getClass().getClassLoader();
        }
        //catalina加载器,父类为commonLoader
        catalinaLoader = createClassLoader("server", commonLoader);
        //shared加载器,父类为commonLoader
        sharedLoader = createClassLoader("shared", commonLoader);
    } catch (Throwable t) {
        handleThrowable(t);
        log.error("Class loader creation threw exception", t);
        System.exit(1);
    }
}

但是创建common加载器时,传入的是null,如何判断common加载器的父类是ApplicationClassLoader呢。

跟到底层会发现获取系统类加载器为父类加载器,即AppClassLoader。其余源码的分析在类加载详解中有进行过分析,这里不再做详细阐述。

三、tomcat类加载流程

· Bootstrap

主要加载JVM启动所需要的类。

· System

主要加载tomcat启动的类,比如bootstrap.jar。位于bin/bootstrap.jar。

· Common

主要加载tomcat中常用的类,位于lib中。

· WebApp

主要加载应用的类文件,即位于WEB-INF/lib下的jar文件和WEB-INF/classes下的class文件。

四、tomcat类加载源码分析

代码语言:javascript
复制
public Class<?> loadClass(String name) throws ClassNotFoundException {
    return loadClass(name, false);
}

//重写了loadClass方法
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

    synchronized (JreCompat.isGraalAvailable() ? this : getClassLoadingLock(name)) {
        if (log.isDebugEnabled())
            log.debug("loadClass(" + name + ", " + resolve + ")");
        Class<?> clazz = null;
        //校验程序是否启动,如果已启动抛出异常
        checkStateForClassLoading(name);
        //校验当前对象缓存是否加载过该类
        clazz = findLoadedClass0(name);
        //如果已经加载过该类 则直接返回
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return clazz;
        }

        //校验是否加载过该类,会调用native方法
        clazz = JreCompat.isGraalAvailable() ? null : 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);
        ClassLoader javaseLoader = getJavaseClassLoader();
        boolean tryLoadingFromJavaseLoader;
        try {

            //使用javaseLoader加载
            // 即-使用ExtClassLoader/BootstrapClassLoader判断是否需要加载这个calss
            // 防止先被webAppClassLoader加载了脏的类
            URL url;
            if (securityManager != null) {
                PrivilegedAction<URL> dp = new PrivilegedJavaseGetResource(resourceName);
                url = AccessController.doPrivileged(dp);
            } else {
                url = javaseLoader.getResource(resourceName);
            }
            tryLoadingFromJavaseLoader = (url != null);
        } catch (Throwable t) {
            ExceptionUtils.handleThrowable(t);
            tryLoadingFromJavaseLoader = true;
        }

        if (tryLoadingFromJavaseLoader) {
            try {
                clazz = javaseLoader.loadClass(name);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return clazz;
                }
            } catch (ClassNotFoundException e) {
            }
        }

        //安全策略校验
        if (securityManager != null) {
            int i = name.lastIndexOf('.');
            if (i >= 0) {
                try {
                    securityManager.checkPackageAccess(name.substring(0, i));
                } catch (SecurityException se) {
                    String error = sm.getString("webappClassLoader.restrictedPackage", name);
                    log.info(error, se);
                    throw new ClassNotFoundException(error, se);
                }
            }
        }

        //判断是否需要被WebAppClassLoader进行加载,是否设置了delegate
        boolean delegateLoad = delegate || filter(name, true);
        //委托给父类 CommonClassLoader加载
        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) {
            }
        }

        //使用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) {
        }

        //WebAppClassLoader加载失败,则委托WebAppClassLoader的父类进行加载
        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);
}

上述源码分析中,tomcat的类加载步骤为(delegate设置为true的情况):

(1)判断当前被加载的类对象是否存在缓存中,如果存在则直接返回该类。

(2)判断是否加载过该类,如果加载过则直接返回该类。

(3)委派jvm中的父类ExtClassLoader、BootStrapClassLoader是否加载过该类。

(4)委派tomcat中的父类CommonClassLoader是否加载过该类。

(5)当前WebAppClassLoader自己加载该类。

(6)如果类仍然没有加载成功,则抛出异常。

五、tomcat打破双亲委派加载机制

不论加载的delegate配置设置为true或者false,tomcat加载类的顺序都为先查询当前WebAppClassLoader类加载器是否加载过该类,而不是预先委托父类进行加载。

第二点是当delegate设置为false时,上述的第四步和第五步可能也会打破双亲委派,优先使用WebAppClassLoader加载器进行加载,加载不到才委托父类进行加载。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2020-07-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 码云大作战 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档