java与tomcat5类加载机制

来源:互联网 发布:淘宝打折工具在哪里 编辑:程序博客网 时间:2024/05/17 03:10

1. java类加载器

近来了解tomcat的类加载机制,所以先回顾一下Java虚拟机类加载器,如果从java虚拟机的角度来看的话,其实类加载器只分为两种:一种是启动类加载器(即Bootstrap ClassLoader),通过使用JNI来实现,我们无法获取到到它的实例;另一种则是java语言实现java.lang.ClassLoader的子类。一般从我们的角度来看,会根据类加载路径会把类加载器分为3种:Bootstrap ClassLoader,ExtClassLoader,AppClassLoader.后两者是sun.misc.Launcher类的内部类,而前者在JDK源码中是没有与之对应的类的,倒是在sun.misc.Launcher中可以看到一些它的加载路径信息。如果找不到sun的源码,可以下载OpenJDK的来看一下。

Bootstrap ClassLoader: 引导类加载器,从%JAVA_RUNTIME_JRE%/lib目录加载,但并不是将该目录所有的类库都加载,它会加载一些符合文件名称的,例如:rt.jar,resources.jar等。在sun.misc.Launcher源码中也可以看得它的加载路径:

private static String bootClassPath = System.getProperty("sun.boot.class.path");
  • 1
  • 1

或者配置-Xbootclasspath参数指定加载的路径,通过获取环境变量sun.boot.class.path看一下到底具体加载了那些类:

D:\Program Files\Java\jdk1.7.0_67\jre\lib\resources.jarD:\Program Files\Java\jdk1.7.0_67\jre\lib\rt.jarD:\Program Files\Java\jdk1.7.0_67\jre\lib\sunrsasign.jarD:\Program Files\Java\jdk1.7.0_67\jre\lib\jsse.jarD:\Program Files\Java\jdk1.7.0_67\jre\lib\jce.jarD:\Program Files\Java\jdk1.7.0_67\jre\lib\charsets.jarD:\Program Files\Java\jdk1.7.0_67\jre\lib\jfr.jarD:\Program Files\Java\jdk1.7.0_67\jre\classes
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

Extension ClassLoader:扩展类加载器,实现类为sun.misc.Launcher$ExtClassLoader,加载%JAVA_RUNTIME_JRE%/lib/ext/目录下的jar包,也可以在sun.misc.Launcher源码中也可以看得它的加载路径:

String s = System.getProperty("java.ext.dirs");
  • 1
  • 1

通过获取java.ext.dirs环境变量打印一下:

D:\Program Files\Java\jdk1.7.0_67\jre\lib\ext
  • 1
  • 1

Appication ClassLoader:应用程序类加载器,或者叫系统类加载器,实现类为sun.misc.Launcher$AppClassLoader。从sun.misc.Launcher的构造函数中可以看到,当AppClassLoader被初始化以后,它会被设置为当前线程的上下文类加载器以及保存到Launcher类的loader属性中,而通过ClassLoader.getSystemClassLoader()获取的也正是该类加载器(Launcher.loader)。应用类加载器从用户类路径中加载类库,可以在源码中看到:

final String s = System.getProperty("java.class.path");
  • 1
  • 1

1.1 类关系

classloader-1

由图看到Bootstrap ClassLoader并不在继承链上,因为它是java虚拟机内置的类加载器,对外不可见。可以看到顶层ClassLoader有一个parent属性,用来表示着类加载器之间的层次关系(双亲委派模型);注意,ExtClassLoader类在初始化时显式指定了parent为null,所以它的父类加载器默认为Bootstrap ClassLoader。在tomcat中都是通过扩展URLClassLoader来实现自己的类加载器。

1.2 双亲委托模型

这3种类加载器之间存在着父子关系(区别于java里的继承),子加载器保存着父加载器的引用。当一个类加载器需要加载一个目标类时,会先委托父加载器去加载,然后父加载器会在自己的加载路径中搜索目标类,父加载器在自己的加载范围中找不到时,才会交还给子加载器加载目标类。

采用双亲委托模式可以避免类加载混乱,而且还将类分层次了,例如java中lang包下的类在jvm启动时就被启动类加载器加载了,而用户一些代码类则由应用程序类加载器(AppClassLoader)加载,基于双亲委托模式,就算用户定义了与lang包中一样的类,最终还是由应用程序类加载器委托给启动类加载器去加载,这个时候启动类加载器发现已经加载过了lang包下的类了,所以两者都不会再重新加载。当然,如果使用者通过自定义的类加载器可以强行打破这种双亲委托模型,但也不会成功的,java安全管理器抛出将会抛出java.lang.SecurityException异常。

classloader-2

  1. 启动类加载器是扩展类加载器的父类加载器:扩展类加载器在sun.misc.Launcher构造函数中被初始化,它的父类加载器被设置了为null,那为什么还说启动类加载器是它的父加载器?看一下ClassLoader.loadClass()方法:
 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) {  // 父类加载器不为null,则调用父类加载器尝试加载                        c = parent.loadClass(name, false);                    } else {   // 父类加载器为null,则调用本地方法,交由启动类加载器加载,所以说ExtClassLoader的父类加载器为Bootstrap ClassLoader                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                }                if (c == null) { //仍然加载不到,只能由本加载器通过findClass去加载                    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;        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

从代码中看到,如果parent==null,将会由启动类加载器尝试加载,所以扩展类加载器的父类加载器是启动类加载器。

  1. 扩展类加载器是应用程序类加载器的父类加载器:这个比较好理解,依然是在sun.misc.Launcher构造函数初始化应用程序类加载器时,指定了ExtClassLoader为AppClassLoader的父类加载器:
loader = AppClassLoader.getAppClassLoader(extcl);//loader是ClassLoader的属性,extcl是扩展类加载器实例
  • 1
  • 1
  1. 应用程序类加载器是自定义类加载器的父类加载器:这里指的是使用默认构造函数进行自定义类加载器(否则 你可以指定parent来构造一个父加载器为ExtClassLoader的自定义类加载器),无论是通过扩展ClassLoader还是URLClassLoader最终都会获取系统类加载器(AppClassLoader)作为父类加载器:
protected ClassLoader() {        //调用getSystemClassLoader方法获取系统类加载器作为父类加载器        this(checkCreateClassLoader(), getSystemClassLoader());     }public static ClassLoader getSystemClassLoader() {        initSystemClassLoader(); //初始化系统类加载器        .....        return scl;    }private static synchronized void initSystemClassLoader() {        ......        sun.misc.Launcher l = sun.misc.Launcher.getLauncher();        ......        scl = l.getClassLoader();  //这里拿到的就是在Launcher构造函数中构造的AppClassLoader实例        ......        }    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

2. tomcat7类加载器

tomcat作为一个Java web容器,也有自己的类加载机制,通过自定义的类加载机制以实现共享类库的抽取,不同web应用之间的资源隔离还有热加载等功能。除了一些java自身的一些类加载器处,它实现的主要类加载器有:Common ClassLoader,Catalina ClassLoader,Shared ClassLoader以及WebApp ClassLoader.通过下面类关系图以及逻辑关系图,同时对比上文内容梳理这些类加载器之间的关系。

2.1 类关系图

classloader-3

从图中看到了Common,Catalina,Shared类加载器是URLClassLoader类的一个实例,只是它们的类加载路径不一样,在tomcat/conf/catalina.properties配置文件中配置(common.loader,server.loader,shared.loader).WebAppClassLoader继承自WebAppClassLoaderBase,基本所有逻辑都在WebAppClassLoaderBase为中实现了,可以看出tomcat的所有类加载器都是以URLClassLoader为基础进行扩展。

2.2 逻辑关系图

classloader-4

上面说到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者为同一个对象    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

在上面的代码初始化时很明确是指出了,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);                    }                });    }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

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

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

 if (getLoader() == null) {            WebappLoader webappLoader = new WebappLoader(getParentClassLoader());            webappLoader.setDelegate(getDelegate());            setLoader(webappLoader);        }
  • 1
  • 2
  • 3
  • 4
  • 5
  • 1
  • 2
  • 3
  • 4
  • 5

这里getParentClassLoader()会获取父容器StandarHost.parentClassLoader对象属性,而这个对象属性是在Catalina$SetParentClassLoaderRule.begin()初始化,初始化的值其实就是Catalina.parentClassLoader对象属性,再来跟踪一下Catalina.parentClassLoader,在Bootstrap.init()时通过反射调用了Catalina.setParentClassLoader(),将Bootstrap.sharedLoader属性设置为Catalina.parentClassLoader,所以WebClassLoader的父类加载器是Shared ClassLoader.

2.3 类加载逻辑

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();等很多地方都一样是违反了双亲委托。

原创粉丝点击