tomcat源码分析—类加载体系

分析Tomcat 的类加载机制之前,还来看看Java 体系的类加载器,在《深入理解Java虚拟机》书中讲到了Java的类加载机制

1:类加载机制

源码编译器将java源代码编译成字节码文件(class文件),字节码文件格式主要分为两部分:常量池和方法字节码。 具体查看 Java 类加载与初始化

java虚拟机中的类加载器将class文件加载到内存中,通过数据验证,解析,初始化等过程来创建j实例。

每个类加载器都有自己的命名空间,命名空间由该加载器及所有父加载器所加载的类组成,如果同一个class文件被两个不同的类加载器加载,那么通过这两个class也就不同的。

public class ClassLoaderTest {

    public static void main(String[] args)
            throws ClassNotFoundException, InstantiationException, IllegalAccessException {
        Product product = new Product(10, "apple");
        System.out.println(product);

        CustomClassLoader classLoader = new CustomClassLoader(null);
        Class<?> class1 = classLoader.loadClass("com.java8.demo.Product");

        System.out.println(class1.newInstance());
        Product product1 = (Product) class1.newInstance();

        System.out.println(product1.getClass());

        System.out.println("END...");

    }

    public static class CustomClassLoader extends ClassLoader {

        public CustomClassLoader(ClassLoader parent) {
            super(parent);
        }

        @SuppressWarnings("resource")
        private byte[] locaClassData(String name) {

            byte[] bytes = new byte[256];
            try {
                name = name.replace(".", "/");
                String fileName = "D:/java/workspace/demos/javacode-demo/target/classes/" + name + ".class";
                FileInputStream inputStream = new FileInputStream(new File(fileName));
                ByteArrayOutputStream outputStream = new ByteArrayOutputStream();

                int len = 0;
                while ((len = inputStream.read(bytes)) > 0) {
                    outputStream.write(bytes, 0, len);
                }
                outputStream.flush();
                return outputStream.toByteArray();

            } catch (Exception e) {
                e.printStackTrace();
                return null;
            }
        }

        @Override
        protected Class<?> findClass(String name) throws ClassNotFoundException {
            byte[] bytes = locaClassData(name);
            return defineClass(name, bytes, 0, bytes.length);
        }
    }
}

输出如下:

com.java8.demo.Product@15db9742
com.java8.demo.Product@70dea4e
Exception in thread "main" java.lang.ClassCastException: com.java8.demo.Product cannot be cast to com.java8.demo.Product
    at com.java8.demo.ClassLoaderTest.main(ClassLoaderTest.java:18)

红色部分可以看出,虽然是同一个class文件,但是通过不同的类加载器生成的类不同。

 

2: 双亲委派机制

Java虚拟机规范中提到的主要有3个类加载器

1:启动类加载器(Bootstrap ClassLoader):这个类加载器加载JAVA_HOME/lib目录中的,或者由-Xbootclasspath参数指定的路径中的jar文件。

2:扩展类加载器(Extension ClassLoader):加载 JAVA_HOME/lib/ext目录下的,或者由System.getProperty("java.ext.dirs")指定的路径中的jar,开发者可以直接使用扩展类加载器。在   使用Java运行程序时,也可以指定其搜索路径,例如:java -Djava.ext.dirs=d:\projects\testproj\classes HelloWorld。

3:应用程序类加载器(Application ClassLoader): 也称系统类加载器,应用程序加载器实现了sun.misc.Launcher$AppClassLoader,它负责加载系统类路径java -classpath或-D         java.class.path 指定路径下的类库,也就是我们经常用到的classpath路径,开发者可以直接使用系统类加载器。一般情况下该类加载是程序中默认的类加载器,通过ClassLoader#getSystemClassLoader()方法可以获取到该类加载器。

 

类加载器的关系图如下:

双亲委派机制的加载过程:

如果一个类加载器需要加载某个类时,它首先不会自己去尝试加载这个类,而是把这个加载的请求委派给父类加载器去完成,父类加载器在委派给上一层的加载器去加载,因此所有的加载请求最终都应该传送到顶层的启动类加载器中,只有当父加载器不能加载置顶的类时(在搜索范围的路径中找不到要加载的类),子加载器才会尝试自己去加载。
 

3: Tomcat的类加载体系

  tomcat中的类加载除了jvm 规范提到的以上三种类加载器之外,还实现了自身的类加载体系。下图展示了Tomcat的类加载体系,上下类加载器之间不是继承关系,而是一种委派关系

 

 

最上面的3个类加载和默认的一致,Common ClassLoader、Catalina ClassLoader、Shared ClassLoader和Webapp ClassLoader则是Tomcat自己定义的类加载器,
它们分别加载tomcat目录中 /common/*/server/*/shared/*/WebApp/WEB-INF/*中的Java类库。
其中WebApp类加载器通常会存在多个实例,每一个Web应用程序对应一个WebApp类加载器, 通过WebApp类加载器也是实现webapp隔离性。
注: Catalina ClassLoader、Shared ClassLoader在tomcat6以后默认合并到了lib/目录中,也就是只需要他俩的父加载器common就可以了,如果要启用,则需要在conf/catalina.properties中配置,比如配置 shared.loader=  配置为
shared.loader="${catalina.base}/shared/lib","${catalina.base}/shared/lib/*.jar"
 
则会启用Shared ClassLoader,并且会加载${catalina.base}/shared/lib中的jar文件。
 
posted @ 2016-08-12 18:11  南极山  阅读(259)  评论(0)    收藏  举报