Tomcat7源码解读(四) —— 类加载器1

综述:在上一节中分析了Tomcat的执行过程。这一节将分析,Tomcat启动以及请求处理过程中所涉及到的类与对象,是由谁加载的,Tomcat的类加载器的特点。

 

4.1 JAVA的类加载过程

1)类加载load:从字节码二进制文件.class文件将类加载到内存将内存中的class放到运行时数据区的方法区内。类的初始化过程会在堆区建立一个java.lang.Class对象,用来封装该类相关的数据结构。

 

2)  连接:连接又分为以下小步骤

    a) 验证:出于安全性的考虑,验证内存中的字节码是否符合JVM的规范,类的结构规范、语义检查、字节码操作是否合法、这个是为了防止用户自己建立一个非法的XX.class文件就进行工作了,或者是JVM版本冲突的问题,比如在JDK6下面编译通过的class(其中包含注解特性的类),是不能在JDK1.4的JVM下运行的。

    b) 准备:将类的静态变量进行分配内存空间、初始化默认值。(对象还没生成呢,所以这个时候没有实例变量什么事情)

    c)  解析:不同的JVM实现可能选择不同的解析策略。一种做法是递归的把所有依赖的形式引用(只是申明,并不适用类)都进行解析。而另外的做法则可能是只在一个形式引用真正需要的时候(也就是说在所引用类的对象被创建)才进行解析。也就是说如果一个Java类只是被引用了,但是并没有被真正用到,那么这个类有可能就不会被解析。当前的JVM一般都采用了第二种策略。

 

 3) 类的初始化: 将类的静态变量赋予正确的初始值,这个初始值是开发者自己定义时赋予的初始值,而不是默认值。

 

4.2 类加载器的加载模式

Parent First加载模式(也就是在加载的时候会委托给父亲类加载器优先加载的模式)只是JVM的一种默认实现,并不是所有的类加载都需要实现该模式的。

 

Parent Last加载模式,Tomcat的应用类加载器,WebappClassLoader,每一个Context都会尝试优先加载所需要的类,只有在无法加载到的时候才委托给父亲加载器完成加载操作。之所以实现这样一种策略为了在多应用的环境中,例如应用A,B使用了默认的数据库连接实现,而应用C需要定制,那么这样C应用中只要包含该实现就可以实现覆盖默认的实现。

 

4.3 Tomcat7的类加载器的层级结构


Tomcat7运行时类的加载说明:

1)Bootstrap Class Loader是JVM的内核由C实现的,加载了JVM的核心包rt.jar。rt.jar中的所有类执行其class的getClassLoader()方法都将返回null,例如String.class.getClassLoader()。

 

2)Extension Class Loader主要加载了JVM扩展包中相关的jar包。例如运行下列代码将System.out.println(ZipPath.class.getClassLoader());将得到如下的运行结果:sun.misc.Launcher$ExtClassLoader

 

3)System Class Loader加载CLASSPATH相关的类,例如在Tomcat的Bootstrap的main方法中执行System.out.println(Bootstrap.class.getClassLoader());则将得到:sun.misc.Launcher$AppClassLoader

 

4)Common Class Loader,Tomcat7中的CATALINA_HOME/lib下的jar包。注意Tomcat在启动文件中将启动时配置了-classpath "%CATALINA_HOME%\lib\catalina.jar"因此catalina.jar中的类虽然指定使用类加载器Common Class Loader,但是按JVM的委托加载原则System.out.println(Bootstrap.class.getClassLoader());得到的类加载器是:sun.misc.Launcher$AppClassLoader。

 

5)Webapp Class Loader, 主要负责加载Context容器中的所有的类。实际上该加载器提供了参数delegateLoad供用户设定是否使用parent-first加载。默认该值为false,默认用parent-last加载。出于安全性的考虑对于核心类WebappClassLoader是不允许加载的。包括:java.,javax.servlet.jsp.jstl,javax.servlet.,javax.el

4.4 类加载器的相关类结构图


 4.5 类加载器的相关源代码

(一)ClassLoader的load方法

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } 
                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    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)在该方法中,首先检查是否已经加载了该类,这里有个问题JVM如何判断一个类是否被加载过的?这里涉及到了类的命名空间问题。在JAVA中判断一个类是否相同不仅看类名是否相同,还要看其类加载器是否相同。同一个类可以被不同的类加载器所加载,并且认为是不同的。该问题可以分解下面两个方面看。

a) 单一加载原则:在加载器链中,一个类只会被链中的某一个加载器加载一次。而不会被重复加载。实现类的共享,Tomcat多个应用,如果需要共享一些jar包,那么只需要交给commonClassLoader加载,那么所有的应用就可以共享这些类。

b) 可见性原则:父加载器加载的类,子加载器是可以访问的。而自加载器所加载的类,父加载器无法访问。不同加载器链之间其是相互不可见,无法访问的。实现隔离,Tomcat就是应用该特性,为每一个Context容器创建一个WebappClassLoader类加载器对象,从而实现了应用间的相互隔离。应用间的类是不可见的所以无法相互访问。

 

2) 如果步骤一中无缓存,查看该类父加载器,如果存在那么委托给付加载器。如果没有父加载器那么认为BootstrapClassLoader是其父加载器,委托进行加载。

 

3)如果父加载器无法加载则抛出ClassNotFoundException,调用抽象方法findClass方法。

 

4)此处的resolveClass方法指的是上文类加载过程中连接的第三步操作。resolve该类的形式引用等等。

 

(二)类URLClassLoader的findClass方法

protected Class<?> findClass(final String name)
         throws ClassNotFoundException
    {
        try {
            return AccessController.doPrivileged(
                new PrivilegedExceptionAction<Class>() {
                    public Class run() throws ClassNotFoundException {
                        String path = name.replace('.', '/').concat(".class");
                        Resource res = ucp.getResource(path, false);
                        if (res != null) {
                            try {
                                return defineClass(name, res);
                            } catch (IOException e) {
                                throw new ClassNotFoundException(name, e);
                            }
                        } else {
                            throw new ClassNotFoundException(name);
                        }
                    }
                }, acc);
        } catch (java.security.PrivilegedActionException pae) {
            throw (ClassNotFoundException) pae.getException();
        }
    }

该方法的核心是获取到JAVA类的字节码,然后调用父类的defineClass方法完成类的构造过程。defineClass是由JVM实现的,不允许被覆写,因此用户类文件就必须遵循JVM的文件规范才能被正确的解析。

 

(三)WebappClassLoader重新覆写了ClassLoader的loadClass方法(删除了部分代码)

@Override
public synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
        Class<?> clazz = null;
		………
        // (0) 当前对象缓存中检查是否已经加载该类
        clazz = findLoadedClass0(name);

        // (0.1) 检查JVM的缓存,是否已经加载过该类
        clazz = findLoadedClass(name);

        // (0.2) 防止加载一些系统相关的类
        try {
            clazz = system.loadClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {}

        boolean delegateLoad = delegate || filter(name);

        // (1) 如果配置了parent-first模式,那么委托给父加载器
        if (delegateLoad) {
            ClassLoader loader = parent;
            if (loader == null) loader = system;
            try {
                clazz = Class.forName(name, false, loader);
                if (clazz != null) {
                    if (resolve) resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {}
        }

        // (2) 从应用环境中查找类,主要是应用下的/lib目录与classes目录
        try {
            clazz = findClass(name);
            if (clazz != null) {
                if (resolve)
                    resolveClass(clazz);
                return (clazz);
            }
        } catch (ClassNotFoundException e) {}

        // (3) 如果在当前应用下无法找到所需要的类,再委托给父加载器加载(parent-last)
        if (!delegateLoad) {
            ClassLoader loader = parent;
            if (loader == null)
                loader = system;
            try {
                clazz = Class.forName(name, false, loader);
                if (clazz != null) {
                    if (resolve)
                        resolveClass(clazz);
                    return (clazz);
                }
            } catch (ClassNotFoundException e) {}
        }

        throw new ClassNotFoundException(name);

    }

 

 

4.6 总结

本节介绍了JVM的类加载器原理,Tomcat的类加载器结构,类加载器的实现,以及类加载器的部分源代码。重点需要理解的是类加载器的命名空间这一设计特性。所以的其他内容都是服务该特性的,从而实现了类加载的安全性,可见性等。

 

 

 

猜你喜欢

转载自t5crambing.iteye.com/blog/1930327