ivaneye.com

ClassLoader

什么是ClassLoader?

Java程序都是由若干个.class文件组成,程序的运行过程即是对class文件的调用。 程序在启动的时候,并不会一次性加载程序所要用的所有class文件,而是根据程序的需要, 通过Java的类加载机制(ClassLoader)来动态加载某个class文件到内存当中的, 只有class文件被载入到了内存之后,才能被其它class所引用。 所以ClassLoader就是用来动态加载class文件到内存当中用的。

Java默认提供的三个ClassLoader

示例:

import com.sun.nio.zipfs.ZipFileStore;
public class Test {
    public static void main(String[] args) {
        System.out.println("1".getClass().getClassLoader());
        System.out.println(ZipFileStore.class.getClassLoader());
        System.out.println(A.class.getClassLoader());
    }
}
class A{}

输出结果:

null                          //BootStrap ClassLoader输出为null
sun.misc.Launcher$ExtClassLoader@3e10c986
sun.misc.Launcher$AppClassLoader@610f7612

双亲委托模型

ClassLoader使用的是双亲委托模型来搜索类的,每个ClassLoader实例都有一个父类加载器的引用(不是继承的关系,是一个包含的关系), 虚拟机内置的类加载器(Bootstrap ClassLoader)本身没有父类加载器,但可以用作其它ClassLoader实例的的父类加载器。

当一个ClassLoader实例需要加载某个类时,它在试图亲自搜索某个类之前,先把这个任务委托给它的父类加载器,这个过程是由上至下依次检查的:

使用双亲委托模型主要是为了安全性.

如果不使用这种委托模式,那我们就可以随时使用自定义的Object来动态替代java核心api中定义的类型, 这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为Object已经在启动时就被引导类加载器(Bootstrcp ClassLoader) 加载,所以用户自定义的ClassLoader永远也无法加载一个自己写的Object,除非你改变JDK中ClassLoader搜索类的默认算法。

class的唯一性判断

JVM在判定两个class是否相同时,不仅要判断两个类名是否相同,而且要判断是否由同一个类加载器实例加载的。 只有两者同时满足的情况下,JVM才认为这两个class是相同的。就算两个class是同一份class字节码, 如果被两个不同的ClassLoader实例所加载,JVM也会认为它们是两个不同class。 比如网络上的一个Java类org.classloader.simple.NetClassLoaderSimple, javac编译之后生成字节码文件NetClassLoaderSimple.class, ClassLoaderA和ClassLoaderB这两个类加载器并读取了NetClassLoaderSimple.class文件, 并分别定义出了java.lang.Class实例来表示这个类,对于JVM来说,它们是两个不同的实例对象, 但它们确实是同一份字节码文件,如果试图将这个Class实例生成具体的对象进行转换时, 就会抛运行时异常java.lang.ClassCaseException,提示这是两个不同的类型。

自定义ClassLoader

Java中提供的默认ClassLoader,只加载指定目录下的jar和class,如果我们想加载其它位置的类或jar时, 比如:要加载网络上的一个class文件,通过动态加载到内存之后,要调用这个类中的方法实现业务逻辑。 在这样的情况下,默认的ClassLoader就不能满足需求,所以需要定义自己的ClassLoader。

定义自已的类加载器:

Tomcat ClassLoader

以Tomcat的ClassLoader为例,来说明自定义ClassLoader的应用

跟其他主流的Java Web服务器一样,Tomcat也拥有不同的自定义类加载器,达到对各种资源库的控制。一般来说,Java Web服务器需要解决以下四个问题:

对于以上几个问题,如果单独使用一个类加载器明显是达不到效果的,必须根据实际使用若干个自定义类加载器。

Tomcat5 ClassLoader:

Tomcat6 ClassLoader:

Tomcat7 ClassLoader:

Tomcat5,6,7的ClassLoader结构略有不同。

Tomcat6中将CommonClassLoader,CatalinaClassLoader,SharedClassLoader合并,就一个CommonClassLoader.

Tomcat7中没有ExtensionClassLoader.

首先看前三个问题:

从上图可以看出,为了解决jar隔离和共享的问题。对于每个webapp,Tomcat都会创建一个WebAppClassLoader来加载应用,这样就保证了每个应用加载进来的class都是不同的(因为ClassLoader不同)! 而共享的jar放在tomcat-home/lib目录下,由CommonClassLoader来加载。通过双亲委托模式,提供给其下的所有webapp共享

特别说明

对于WebAppClassLoader是违反双亲委托模型的。如果加载的是jre或者servlet api则依然是双亲委托模型。 而如果不是的话,则会先尝试自行加载,如果找不到再委托父加载器加载。 这个应该是可以理解的,因为应用的class是由WebAppClassLoader加载的,是项目私有的。

对于热部署功能,Tomcat只是提供了后台线程进行扫描,如果有修改即重新加载!

public void backgroundProcess() {
    if (reloadable && modified()) {
        try {
            Thread.currentThread().setContextClassLoader
                (WebappLoader.class.getClassLoader());
            if (container instanceof StandardContext) {
                ((StandardContext) container).reload();
            }
        } finally {
            if (container.getLoader() != null) {
                Thread.currentThread().setContextClassLoader
                    (container.getLoader().getClassLoader());
            }
        }
    } else {
        closeJARs(false);
    }
}
public synchronized void reload() {
    if (!started)
        throw new IllegalStateException
            (sm.getString("containerBase.notStarted", logName()));
    if(log.isInfoEnabled())
        log.info(sm.getString("standardContext.reloadingStarted",
                getName()));
    setPaused(true);
    try {
        stop();
    } catch (LifecycleException e) {
        log.error(sm.getString("standardContext.stoppingContext",
                getName()), e);
    }
    try {
        start();
    } catch (LifecycleException e) {
        log.error(sm.getString("standardContext.startingContext",
                getName()), e);
    }
    setPaused(false);
}

OSGi ClassLoader

对于Tomcat来说,其ClassLoader结构,大体上还是符合双亲委托模型的。

而OSGi则完全打破了双亲委托模型,自行实现了一套网状模型。

究其原因主要是为了实现模块化功能!

其Class加载流程如下:

这么做有两个原因:

  1. 唯一能够定义java.*包的类加载器是bootstrap类加载器,这个规则是JVM要求的。如果OSGI bundle类加载器试图加载这种类,则会抛Security Exception。
  2. 一些JVM错误地假设父加载器委托永远会发生,内部VM类就能够通过任何类加载器找到特定的其他内部类。所以OSGi提供了org.osgi.framework.bootdelegation属性,允许对特定的包(即那些内部VM类)使用父加载器委托。

什么是fragment?

Fragment bundle是OSGi 4引入的概念,它是一种不完整的bundle,必须要附加到一个host bundle上才能工作;fragment能够为host bundle添加类或资源, 在运行时,fragment中的类会合并到host bundle的内部classpath中。