Tomcat源码分析:类加载器

不同类加载器加载的类之间是不兼容的(继承,实现,强制转换等),相当于jvm通过类加载机制提供了不同的类空间。

在进行程序的架构设计时,可以通过类加载机制实现更好的模块划分和更优的实现,在tomcat7的代码中,为了优化内存和实现不同应用程序之间的隔离,tomcat实现了一些自定义的类加载器来完成这些功能。


1、jvm的类加载机制

JVM中通常有三层默认的类加载器:

       1) 根类加载器(Bootstrapc++实现) 
       2) 
扩展类加载器(Extension,java实现) 
      
3) 系统类加载器(System,java实现) 

这三者间都是父子关系,即前者是后者的父亲或者双亲类加载器,并由此构建了一个“双亲委派关系”,或叫“代理”关系。


2ClassLoader

ClassLoader类有如下三个关键方法:

        1) loaderClass(String name,Boolean resolve):该方法为ClassLoader的入口点,根据指定的二进制名称来加载类,系统就是调用ClassLoader的此方法来获取指定类对应的Class对象。

        2) findClass(String name):根据二进制名称来查找类。

        3) Class defineClass(String name,byte[] b,int off,int len)该方法负责将指定类的字节码文件(即Class文件)读入字节数组byte[]b,并把它转化为Class对象,该字节码文件可以来源于文件,网络等。

实现自定义的ClassLoader,可以通过重写以上两个方法来实现,一般重写findClass()方法,而不是重写loadClass()方法,因为loadClass()方法的执行步骤如下:

        1) findLoadedClass(String )来检查是否已经加载类,如果已经加载则直接返回。

        2) 在父类加载器上调用loadClass方法,如果父类加载器为null,则使用根类加载器来加载

        3) 调用findClass(String)方法查找类

从以上执行过程可以看出,重写findClass()方法可以避免覆盖默认类加载器的父类委托,缓冲机制两种策略,如果重写loadClass()方法,则需要自己实现。


3tomcat基础类加载器

Tomcat是一个Java工程,也是在jvm提供的几层类加载器下运行起来的。Tomcat中的一些最基本的类,也和其它Java工程一样,是通过jvm的类加载器来加载的,比如tomcat/bin目录下的bootstrap.jartomcat-juli.jarcommons-daemon.jar这几个jar包中的类。

Tomcat启动时,会定义和初始化3个类加载器,commonLoadercatalinaLoadersharedLoader。他们的加载范围是在catalina.properties配置文件中配置的。

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

其中最重要的一个类加载器是commonLoader,它的父类加载器是AppClassLoader,负责加载 $CATALINA_BASE/lib  $CATALINA_HOME/lib两个目录下所有的.class.jar文件。

Tomcat7中,catalinaLoadersharedLoader这两个类加载器也存在,只是默认情况下catalina.properties配置文件没有对server.loadershare.loader两项进行配置,所以在程序里,这两个类加载器实例就被赋值为commonLoader,即一个tomcat实例其实就只有commonLoader实例。

Tomcat启动时初始化类加载器的代码:

    private void initClassLoaders() {
        try {
            //创建commonLoader
            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();
            }
            //commonLoader作为参数传进createClassLoader方法
            catalinaLoader = createClassLoader("server", commonLoader);
            sharedLoader = createClassLoader("shared", commonLoader);
        } catch (Throwable t) {
            handleThrowable(t);
            log.error("Class loader creation threw exception", t);
            System.exit(1);
        }
    }

首先创建commonLoader,再把commonLoader作为参数传给createClassLoader方法里,此方法中会根据catalina.properties中的server.loadershare.loader属性是否为空判断是否创建新的类加载器,如果属性为空则把commonLoader直接赋值给catalinaLoadersharedLoader。否则创建新的类加载器,其父类加载器为commonLoader

private ClassLoader createClassLoader(String name, ClassLoader parent)
        throws Exception {
       //catalina.properties配置文件中的对应项
        String value = CatalinaProperties.getProperty(name + ".loader");
        if ((value == null) || (value.equals("")))
            return parent;
        //${catalina.base}等替换为绝对路径
        value = replace(value);
 
        List repositories = new ArrayList();
        StringTokenizer tokenizer = new StringTokenizer(value, ",");//拆分
        while (tokenizer.hasMoreElements()) {
            String repository = tokenizer.nextToken().trim();
            if (repository.length() == 0) {
                continue;
            }
            // …Check for a JAR URL repository
            //将加载路径封装为Repository对象,根据路径后缀分为DIR,GLOB,JAR,URL四种类型
            if (repository.endsWith("*.jar")) {
                repository = repository.substring
                    (0, repository.length() - "*.jar".length());
                repositories.add(
                        new Repository(repository, RepositoryType.GLOB));
            } else if (repository.endsWith(".jar")) {
                repositories.add(
                        new Repository(repository, RepositoryType.JAR));
            } else {
                repositories.add(
                        new Repository(repository, RepositoryType.DIR));
            }
        }
        //创建类加载器
        return ClassLoaderFactory.createClassLoader(repositories, parent);
    }

在这个方法中,将创建类加载器的任务交给ClassLoaderFactorycreateClassLoader方法实现,该方法最终会通过URLClassLoader类的构造方法创建类加载器,因此commonLoaderURLClassLoader类的实例,而URLClassLoader最终继承于java.lang.ClassLoader


4WebappLoader类加载器

Tomcat中可以部署多个应用程序,应用程序的部署可以通过server.xml中的context标签完成。在具体的tomcat源码中,Context对象表示一个应用程序,它使用WebappLoader类中的WebappClassLoader加载应用,WebappLoader继承了Lifecycle接口,由tomcat进行生命周期管理。

StandardContext启动部分的代码:

protected synchronized void startInternal() throws LifecycleException {
        //...
        if (getLoader() == null) {
            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());
            webappLoader.setDelegate(getDelegate());
            setLoader(webappLoader);
        }
        //...
        try {
            if (ok) {
               
                // Start our subordinate components, if any
                if ((loader != null) && (loader instanceof Lifecycle))
                    ((Lifecycle) loader).start();
                //...
            }
        }catch(..){
             //...
        }
        //...
}

启动StandardContext时,会初始化和启动WebappLoader的实例loader。在此方法中,通过((Lifecycle) loader).start()方法创建WebappClassLoader类型的类加载器。

protected void startInternal() throws LifecycleException {
                 //...
                 // Construct a class loader based on our current repositories list
                 try {
                     classLoader = createClassLoader();
                     classLoader.setResources(container.getResources());
                     classLoader.setDelegate(this.delegate);
                     classLoader.setSearchExternalFirst(searchExternalFirst);
                     if (container instanceof StandardContext) {
                         classLoader.setAntiJARLocking(
                                 ((StandardContext) container).getAntiJARLocking());
                         classLoader.setClearReferencesStatic(
                                 ((StandardContext) container).getClearReferencesStatic());
                         classLoader.setClearReferencesStopThreads(
                                 ((StandardContext) container).getClearReferencesStopThreads());
                         classLoader.setClearReferencesStopTimerThreads(
                                 ((StandardContext) container).getClearReferencesStopTimerThreads());
                         classLoader.setClearReferencesHttpClientKeepAliveThread(
                                 ((StandardContext) container).getClearReferencesHttpClientKeepAliveThread());
                     }
 
                     for (int i = 0; i < repositories.length; i++) {
                         classLoader.addRepository(repositories[i]);
                     }
                     // Configure our repositories
                     setRepositories();
                     setClassPath();
                     setPermissions();
                     ((Lifecycle) classLoader).start();
                     //...
                 } catch (Throwable t) {
                    //...
                 }
                 //...
   }

WebappLoader中保存的类加载器WebappClassLoader,是Tomcat最重要的类加载器,它创造了各个应用程序空间。

通常实现自定义的类加载器重写findClass方法就可以了,这里由于实现的需要,重写loadClass()方法,打破了系统默认的“双亲委托”机制,自定义了一套加载流程:

默认情况下,对于一个未加载过的类,WebappClassLoader会先让系统加载java.lang.ObjectJava本身的基础类,如果不是java本身的类则优先在当前应用Context范围内查找并加载,如果不能加载,再交给commonLoader走标准的双亲委派机制加载。每个WebappLoader加载一个Web程序,加载路径为/WebApp/WEB-INF目录。

public Class loadClass(String name, boolean resolve) throws ClassNotFoundException {
        //...
                   Class clazz = null;
        // (0) 检查本地缓存中是否加载过
        clazz = findLoadedClass0(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }
        // (0.1)检查父类加载器是否已加载过
        clazz = findLoadedClass(name);
        if (clazz != null) {
            if (log.isDebugEnabled())
                log.debug("  Returning class from cache");
            if (resolve)
                resolveClass(clazz);
            return (clazz);
        }
        // (0.2) 先尝试用jvm的系统类加载器加载此类,以防止应用中的类覆盖系统类
        try {
            clazz = j2seClassLoader.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的ClassLoader建议先由parent去load,load不到自己再去加载,
         * 而Servlet规范的建议则恰好相反,Tomcat的实现则做个折中,由用户去决定(context的 delegate定义),
         * 默认使用Servlet规范的建议,即delegate=false
         */
        boolean delegateLoad = delegate || filter(name);
        // (1) delegateLoad为true时,先由parent去尝试加载
        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
            }
        }
        // (2) findClass搜索本地目录WEB-INF/lib和WEB-INF/classes,默认是先搜索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) {
            // Ignore
        }
        // (3) 由parent再尝试加载一下 
        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应用程序都有单独的WebAppClassLoader,一个tomcat中的多个应用之间就可以相互隔离。因此可以使tomcat不受应用程序影响,commonLoaderWebAppClassLoader的父加载器,它的存在可以使多个应用之间共享类库。

 

参考:

http://cuda.itpub.net/29900383/viewspace-1735725/

http://www.blogjava.net/zhuxing/archive/2008/08/08/220841.html


网易云新用户大礼包:https://www.163yun.com/gift

本文来自网易实践者社区,经作者何欢授权发布。