Tomcat ClassLoader详解

5 篇文章 0 订阅
3 篇文章 0 订阅

       记得前几天写getResourceAsStream()提到后面有时间整理下tomcat的ClassLoader的加载结构。今天有闲暇,笔者就来聊聊我认为的tomcat ClassLoader。

       首先提到classloader,笔者首先想到的树的概念,每一层classloader都讲究着寻根方式,一般而言是一个类加载到jvm虚拟机的过程,那么对于一个类是否“相同”,取决于这两个类的类加载器是否相同,尽管一个类来源于同一个class文件,同一个jvm虚拟机加载,但是他们的classloader不一样,那么他们的类就是不相等的。以下面图1为例,可见当前的com.bes.enterprise.webtier.core.DefaultContext被CommonClassLoader加载,而该classloader的hash值为3967e60c。因此不同的classloader他们的hash值是不一样的,所以就会存在类在加载过程中的差异,那么为什么同一个tomcat部署多个应用,而应用中存在相同的jar及相同的类,也不会发生冲突,正是因为classloader的隔离,每个应用有独立的一个WebappClassLoader加载,他们相互隔离,因此不会出现类冲突的问题。基于具体的隔离原理,后续在做解释。

                                                                 图1

Tomcat的ClassLoader层级(对与树形结构图可以查看博文下方图8的结构):

    BootstrapClassLoader(启动类加载器):属于jdk层面的classloader,最顶层的classloader主要加载:${JAVA_HOME}/lib目录下的jar包,主要有rt.jar,resources.jar等。而rt.jar包含但不限于我们常用的java.util,java.lang,java.nio,java.io等包。BootstrapClassLoader的代码实现是native的,不是一个普通的java类,底层由c实现,因此jvm启动的时候,bootstrapClassLoader也会被同时带起。或者配置-XbootClasspath参数指定的路径也会被同时加载。查看下面图9我们通过jinfo pid | findstr sun.boot.class.path可以查看到bootstrapClassLoader主要加载了来自于${JAVA_HOME}/lib目录下的内容。(补充说明windows下用findstr linux下查看使用jinfo pid | grep name即可)

    ExtensionClassLoader(扩展类加载器):隶属于sun.misc.Launcher的内部类,该classLoader继承了URLClassLoader,主要负责加载${JAVA_HOME}/lib/ext目录下的,或者加载系统属性java.ext.dirs中定义的目录。查看下面图9我们通过jinfo pid | findstr java.ext.dirs可以查看到ExtClassLoader主要加载了来自于${JAVA_HOME}/lib/ext的目录以及系统属性定义的目录或者jar。

     ApplicationClassLoader(应用类加载器):隶属于sun.misc.Launcher的内部类,该classLoader继承了URLClassLoader,主要负责加载classpath环境变量定义的目录和jar,或者加载系统属性java.class.path中定义的目录。根据下面图9可以查看到Tomcat的AppClassLoader主要加载了来自于tomcat/bin下的jar包。

                                                                 图2

    CommonClassLoader(通用类加载器):隶属于org.apache.catalina.startup.Bootstrap类,tomcat的自有的classloader。主要负责加载${catalina.base}/lib定义的目录和jar以及${catalina.home}/lib定义的目录和jar,也可自定义配置,具体配置可见conf/catalina.properties中,里面的common.loader里指定了该classloader加载的jar和目录。

    SharedClassLoader(共享类加载器):隶属于org.apache.catalina.startup.Bootstrap类,tomcat的自有的classloader。这个classloader功能主要用于多个web应用可能共享某些目录或者jar,这样你配置在shared.loader中,tomcat会将这些资源加载到sharedclassLoader中,用于实现应用层之间的共享。具体配置也是conf/catalina.properties中,里面的shared.loader里指定了该classloader加载的jar和目录。tomcat默认未配置该值。

    CatalinaClassLoader:隶属于org.apache.catalina.startup.Bootstrap类,tomcat的自有的classloader。该classloader的加载和web应用隔离,加载的jar不会互相影响,可以查看下面图的层次结构,可以看到CatalinaclassLoader的parent为CommonClassLoader。用于实现tomcat需要的资源补充的加载。具体配置也是conf/catalina.properties中,里面的server.loader里指定了该classloader加载的jar和目录。tomcat默认未配置该值。

对于当前3个tomcat自有的classloader的创建过程,我们可以看下图的具体的org.apache.catalina.startup.Bootstrap类。tomcat初始化时会默认初始化这3个classloader。具体逻辑也很清晰。

                                                                           图3

WebappClassLoaderBase(web应用的类加载器)(该tomcat版本为tomcat_8_5_50,tomat7没有WebappClassLoaderBase,直接搜WebappClassLoader的即可。):tomcat加载应用的核心类加载器,可以查看web容器启动的过程中,对于tomcat的应用隔离主要是每个应用都存在一份WebAppClassLoader,因此WebAppClassLoader的创建工作也是在Context(上下文)中进行的,当前方法可以查看org.apache.catalina.core.StandardContext类.整个web容器生命周期启动时,会调用该startInternal()方法,当当前的classloader为null时,会创建一个WebappClassLoader用于加载资源。而WebappClassLoader的启动可以查看下图中的start()方法。会将/WEB-INF/classes目录下的资源和/WEB-INF/lib下的jar的资源全部合并到一起去,交由WebappClassLoaderBase加载。

                                                                                   图4

                                                                          图5 

    而我们知道要想加载自定义资源,那么自有的classloader需要重写loadclass()方法。 那么我们来看下面代码块中webappclassloaderbase的loadclass()实现,以及他是否遵循双亲委派模型。在这里就不得不提之前文章也提到的delegate的概念,tomcat配置delegate的方式在tomcat中配置delegate为true即可具体配置在tomcat的context.xml中配置<Loader delegate="true" />即可,即向上委托,所有的类优先交由上层的classloader去加载。这就是我们解决当应用中的jar和tomcat的jar冲突时的一种手段。

双亲委派模型要求除了顶层的启动类加载器之外,其余的类加载器都应当由自己的父类加载器加载。

所以tomcat设计强大之处,可以自由实现应用的当前类,由哪个classloader优先加载,tomcat的应用之间由于相互隔离,所以classes和lib的资源默认由当前WebappClassLoader加载,所以tomcat也没有完全遵循双亲委派模型。

@Override
 public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {

        synchronized (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;
            }

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

            ClassLoader javaseLoader = getJavaseClassLoader();
            boolean tryLoadingFromJavaseLoader;
            try {

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

            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);
                    }
                }
            }

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

            // 当delegate配置为true,将委托给父级classloader优先加载,当父级classloader加载不 
            //到时才会继续交由当前classloader加载。
            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
                }
            }

            // 查看本地资源
            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
            }

            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);
    }

       问题1:如果tomcat 的 CommonClassLoader 想加载 WebAppClassLoader 中的类,怎么解决?

 这个我们可以查看双亲委派模型被打破的案例,第一种很显然就是重写loadClass()方法即可。第二种就是我们可以使用线程上下文类加载器实现,使用线程上下文加载器,可以让父类加载器请求子类加载器去完成类加载的动作。

     问题2:ExtensionClassLoader中的parent是null,那么为什么他会和bootstrapClassLoader建立了联系?

对于这个问题的解释:有兴趣的可以看ClassLoader的源代码:下面截图是对于这个问题的解释。在classloader中,当加载类时,发现当前classloader的parent classloader为null,那么jvm默认认为他就去寻找根classloader即bootstrapClassloader具体可以findBootstrapClssOrNull(name),查看native的上面注释可知,如果没找到即返回null,再交由下面去加载。这也是常说的双亲委派模型的实现:如图6。

 

                                                                                              图6

                                                                                             图7

 

补充说明:

SecureClassLoader 此类来自于java.security包,扩展了ClassLoader,并为定义具有相关代码源和权限的类提供了额外支持,这些代码源和权限默认情况下由系统策略检索。

URLClassLoader  此类来自于java.net包,扩展了SecureClassLoader,该类加载器用于从指向 JAR 文件和目录的 URL 的搜索路径加载类和资源。这里假定任何以 '/' 结束的 URL 都是指向目录的。如果不是以该字符结束,则认为该 URL 指向一个将根据需要打开的 JAR 文件。

                                                                                                                                       图8 

                                                                             图9

    后记:classloader 作为jvm的重要一环,需要理解还需要多去扒JDK以及tomcat的源码,在调试和阅读过程中才能更好的理解classloader的真正含义。希望和大家一起进步,走的更高更远!

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值