java类加载器-Tomcat类加载器

系统 1750 0

  在上文中,已经介绍了 系统类加载器 以及类加载器的相关机制,还自定制类加载器的方式。接下来就以tomcat6为例看看tomat是如何使用自定制类加载器的。(本介绍是基于tomcat6.0.41,不同版本可能存在差异!)

网上所描述的tomcat类加载器

  在网上搜一下“tomcat类加载器”会发现有大量的文章,在此我偷个懒,^_^把网上对tomcat类加载器的描述重说一下吧。

java类加载器-Tomcat类加载器

  • 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异常了。
  • JasperLoader:每个JSP一个JasperLoader实例,与WebappClassLoader做法类似,JSP支持修改生效是通过丢弃旧的JasperLoader,建一个新的JasperLoader来做到的,同样的,存在轻微的PermSpace的内存泄露的情况

以上对个个classloader的作用做了介绍,但请读者不要搞混淆了,上边说的个个类加载器只是类加载器的名字,不是类加载类的名字。上边的图是看到网上资料的说明绘制的,但是与实际源码中的结构还是差异挺大的。(没有研究是不是因为tomcat的版本所致)。下面就详细介绍下tomcat源码中类加载器的组织结构。

tomcat源码中类加载器的结构分析

首先要说明是tomcat默认配置下的情况。那接下来看看tomcat启动时的类初始化情况,这是BootStrap类的类初始化方法:

  

        private void initClassLoaders() {

        try {

            commonLoader = createClassLoader("common", null);

            if( commonLoader == null ) {

                // no config file, default to this loader - we might be in a 'single' env.

                commonLoader=this.getClass().getClassLoader();

            }

            catalinaLoader = createClassLoader("server", commonLoader);

            sharedLoader = createClassLoader("shared", commonLoader);

        } catch (Throwable t) {

            log.error("Class loader creation threw exception", t);

            System.exit(1);

        }

    }


    

 

可以看到,在创建commonLoader时传的父类加载器是null。跟踪下去会发现commonLoader的父类加载器确实是null。有朋友可能想,tomcat在启动时肯定也要依赖jdk核心库,parent是null那怎么委托给parent去加载核心库的类了啊。这里大家不要忘了ClassLoader类的loadClass方法:

      try {

                    if (parent != null) {

                        c = parent.loadClass(name, false);

                    } else {

                        c = findBootstrapClassOrNull(name);//没有父类加载器时使用bootstrap类加载器

                    }

                } catch (ClassNotFoundException e) {

                    // ClassNotFoundException thrown if class not found

                    // from the non-null parent class loader

                }


    

通过以上initClassLoaders方法我们也能看到catalinaLoader和sharedLoader的父类加载器都是commonLoader,跟上边图的类加载器结构符合。但是commonLoader、catalinaLoader和sharedLoader的创建都是依赖tomcat安装目录下conf/catalina.properties的配置。默认情况配置是:

  • common.loader=${catalina.base}/lib,${catalina.base}/lib/*.jar,${catalina.home}/lib,${catalina.home}/lib/*.jar
  • server.loader=
  • shared.loader=

由于server.loader和shared.loader的配置为空,所以其实commonLoader、catalinaLoader和sharedLoader都是指向同一个类加载器实例,看代码如下:(限于篇幅只贴部分代码)

       private ClassLoader createClassLoader(String name, ClassLoader parent)

        throws Exception {



        String value = CatalinaProperties.getProperty(name + ".loader");

        if ((value == null) || (value.equals("")))

            return parent;


    

而他们指向那个类加载器类的实例呢?跟踪到最后我们发现如下代码:

       StandardClassLoader classLoader = null;

        if (parent == null)

            classLoader = new StandardClassLoader(array);

        else

            classLoader = new StandardClassLoader(array, parent);

        return (classLoader);


    

就是StandardClassLoader的实例,下文再对StandardClassLoader进行源码讲解。

  接下来再看看webappclassloader的创建过程,webappclassLoader是在WebappLoader类中的createClassLoader方法中通过反射实例化的。下边是源代码:

          private WebappClassLoader createClassLoader()

        throws Exception {



        Class clazz = Class.forName(loaderClass);//loaderClass="org.apache.catalina.loader.WebappClassLoader"

        WebappClassLoader classLoader = null;



        if (parentClassLoader == null) {

            parentClassLoader = container.getParentClassLoader();

        }

        Class[] argTypes = { ClassLoader.class };

        Object[] args = { parentClassLoader };

        Constructor constr = clazz.getConstructor(argTypes);

        classLoader = (WebappClassLoader) constr.newInstance(args);



        return classLoader;



    }


    

可以看到他的parent是通过调用container.getParentlassLoader()获得的(如果对tomcat的结构不熟悉,请看这篇 文章 )跟踪到最后我们发现它调用了ContainerBase的这个方法:

          public ClassLoader getParentClassLoader() {

        if (parentClassLoader != null)

            return (parentClassLoader);

        if (parent != null) {

            return (parent.getParentClassLoader());

        }

        return (ClassLoader.getSystemClassLoader());



    }


    

通过默认配置下debug可以知道最后是返回的systemclassloader,也就是说WebappClassLoader的父类加载器是systemclassloader也就是 上篇文章 说的App ClassLoader。

(由于JasperLoader本人还没有做分析,先不进行讲解了)

tomcat类加载器的实现方式分析

  上文说到了commonLoader、catalinaLoader和sharedLoader都是指向StandardClassLoader的实例,来先看一看StandardClassLoader的源码实现:

      public class StandardClassLoader

    extends URLClassLoader

    implements StandardClassLoaderMBean {



	public StandardClassLoader(URL repositories[]) {

        super(repositories);

    }



    public StandardClassLoader(URL repositories[], ClassLoader parent) {

        super(repositories, parent);

    }



}


    

有没有感到你的意外啊,对的就是这么简单,这跟我 上篇文章 说的最简单的实现方式一样。(上篇文章做了解读,这里不再做说明了)

  我们再来看看webappclassLoader,他的实现类就是org.apache.catalina.loader.WebappClassLoader,此类加载器也是继承自URLClassLoader,但是它覆盖了loadClass方法和findClass方法。这个类有三千多行这里就不将代码全部贴出来了。

      public Class loadClass(String name, boolean resolve)

        throws ClassNotFoundException {



        if (log.isDebugEnabled())

            log.debug("loadClass(" + name + ", " + resolve + ")");

        Class clazz = null;



        // Log access to stopped classloader

        if (!started) {

            try {

                throw new IllegalStateException();

            } catch (IllegalStateException e) {

                log.info(sm.getString("webappClassLoader.stopped", name), e);

            }

        }



        // (0) 检查WebappClassLoader之前是否已经load过这个资源

clazz = findLoadedClass0(name);

        if (clazz != null) {

            if (log.isDebugEnabled())

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

            if (resolve)

                resolveClass(clazz);

            return (clazz);

        }



        // (0.1) 检查ClassLoader之前是否已经load过

        clazz = findLoadedClass(name);

        if (clazz != null) {

            if (log.isDebugEnabled())

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

            if (resolve)

                resolveClass(clazz);

            return (clazz);

        }



        // (0.2) 先检查系统ClassLoader,因此WEB-INF/lib和WEB-INF/classes或{tomcat}/libs下的类定义不能覆盖JVM 底层能够查找到的定义(譬如不能通过定义java.lang.Integer替代底层的实现

        try {

            clazz = system.loadClass(name);

            if (clazz != null) {

                if (resolve)

                    resolveClass(clazz);

                return (clazz);

            }

        } catch (ClassNotFoundException e) {

            // Ignore

        }



        // (0.5) Permission to access this class when using a SecurityManager

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

                }

            }

        }



        //这是一个很奇怪的定义,JVM的类加载机制建议先由parent去load,load不到自己再去load(见上篇文章),而Servelet规范的建议则恰好相反,Tomcat的实现则做个折中,由用户去决定(context的 delegate定义),默认使用Servlet规范的建议,即delegate=false

        boolean delegateLoad = delegate || filter(name);



        // (1) 先由parent去尝试加载,如上说明,除非设置了delegate,否则这里不执行

        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) 到WEB-INF/lib和WEB-INF/classes目录去搜索,细节部分可以再看一下findClass,会发现默认是先搜索WEB-INF/classes后搜索WEB-INF/lib

        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) 由parent再去尝试加载一下

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

    }


    

 

java类加载器-Tomcat类加载器


更多文章、技术交流、商务合作、联系博主

微信扫码或搜索:z360901061

微信扫一扫加我为好友

QQ号联系: 360901061

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描下面二维码支持博主2元、5元、10元、20元等您想捐的金额吧,狠狠点击下面给点支持吧,站长非常感激您!手机微信长按不能支付解决办法:请将微信支付二维码保存到相册,切换到微信,然后点击微信右上角扫一扫功能,选择支付二维码完成支付。

【本文对您有帮助就好】

您的支持是博主写作最大的动力,如果您喜欢我的文章,感觉我的文章对您有帮助,请用微信扫描上面二维码支持博主2元、5元、10元、自定义金额等您想捐的金额吧,站长会非常 感谢您的哦!!!

发表我的评论
最新评论 总共0条评论