##默认的三个类加载器 Java默认是有三个ClassLoader,按层次关系从上到下依次是: - Bootstrap ClassLoader - Ext ClassLoader - System ClassLoader Bootstrap ClassLoader是最顶层的ClassLoader,它比较特殊,是用C++编写集成在JVM中的,是JVM启动的时候用来加载一些核心类的,好比:`rt.jar`,`resources.jar`,`charsets.jar`,`jce.jar`等,能够运行下面代码看都有哪些: ``` URL[] urls = sun.misc.Launcher.getBootstrapClassPath().getURLs(); for (int i = 0; i < urls.length; i++) { System.out.println(urls[i].toExternalForm()); } ``` 其他两个ClassLoader都是继承自`ClassLoader`这个类。Java的类加载采用了一种叫作“双亲委托”的方式(稍后解释),因此除了`Bootstrap ClassLoader`其他的ClassLoader都有一个“父”类加载器, 不是经过继承,而是一种包含的关系。 ``` //ClassLoader.java public abstract class ClassLoader { ... // The parent class loader for delegation private ClassLoader parent; ... ``` ##“双亲委托” 所谓“双亲委托”就是当加载一个类的时候会先委托给父类加载器去加载,当父类加载器没法加载的时候再尝试本身去加载,因此整个类的加载是“自上而下”的,若是都没有加载到则抛出`ClassNotFoundException`异常。 上面提到Bootstrap ClassLoader是最顶层的类加载器,实际上Ext ClassLoader和System ClassLoader就是一开始被它加载的。 Ext ClassLoader称为扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目录下的全部的jar(包括本身手动放进去的jar包)。 System ClassLoader叫作系统类加载器,负责加载应用程序classpath目录下的全部jar和class文件,包括咱们平时运行jar包指定cp参数下的jar包。 运行下面的代码能够验证上面内容: ``` ClassLoader loader = Debug.class.getClassLoader(); while(loader != null) { System.out.println(loader); loader = loader.getParent(); } System.out.println(loader); ``` ##“双亲委托”的做用 之因此采用“双亲委托”这种方式主要是为了安全性,避免用户本身编写的类动态替换Java的一些核心类,好比String,同时也避免了重复加载,由于JVM中区分不一样类,不单单是根据类名,相同的class文件被不一样的ClassLoader加载就是不一样的两个类,若是相互转型的话会抛`java.lang.ClassCaseException`. ##自定义类加载器 除了上面说的三种默认的类加载器,用户能够经过继承`ClassLoader`类来建立自定义的类加载器,之因此须要自定义类加载器是由于有时候咱们须要经过一些特殊的途径建立类,好比网络。 至于自定义类加载器是如何发挥做用的,`ClassLoader`类的loadClass方法已经把算法定义了: ``` protected synchronized Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { // 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); } } catch (ClassNotFoundException e) { // ClassNotFoundException thrown if class not found // from the non-null parent class loader } if (c == null) { // If still not found, then invoke findClass in order // to find the class. c = findClass(name); } } if (resolve) { resolveClass(c); } return c; } ``` >1. Invoke `findLoadedClass(String)` to check if the class has already been loaded. >2. Invoke the loadClass method on the parent class loader. If the parent is null the class loader built-in to the virtual machine is used, instead. >3. Invoke the `findClass(String)` method to find the class. 看上面的Javadoc能够知道,自定义的类加载器只要重载`findClass`就行了。 ##Context ClassLoader 首先Java中ClassLoader就上面提到的四种,`Bootstrap ClassLoader`,`Ext ClassLoader`,`System ClassLoader`以及用户自定义的,因此`Context ClassLoader`并非一种新的类加载器,确定是这四种的一种。 首先关于类的加载补充一点就是若是类A是被一个加载器加载的,那么类A中引用的B也是由这个加载器加载的(若是B尚未被加载的话),一般状况下就是类B必须在类A的classpath下。 可是考虑多线程环境下不一样的对象多是由不一样的ClassLoader加载的,那么当一个由ClassLoaderC加载的对象A从一个线程被传到另外一个线程ThreadB中,而ThreadB是由ClassLoaderD加载的,这时候若是A想获取除了本身的classpath之外的资源的话,它就能够经过`Thread.currentThread().getContextClassLoader()`来获取线程上下文的ClassLoader了,通常就是ClassLoaderD了,能够经过`Thread.currentThread().setContextClassLoader(ClassLoader)`来显示的设置。 ##为何要有Context ClassLoader 之因此有Context ClassLoader是由于Java的这种“双亲委托”机制是有局限性的: - 举网上的一个例子: > JNDI为例,JNDI的类是由bootstrap ClassLoader从rt.jar中间载入的,可是JNDI具体的核心驱动是由正式的实现提供的,而且一般会处于-cp参数之下(注:也就是默认的System ClassLoader管理),这就要求bootstartp ClassLoader去载入只有SystemClassLoader可见的类,正常的逻辑就没办法处理。怎么办呢?parent能够经过得到当前调用Thread的方法得到调用线程的>Context ClassLoder 来载入类。 - 我上面提到的加载资源的例子。 `Contex ClassLoader`提供了一个突破这种机制的后门。 Context ClassLoader通常在一些框架代码中用的比较多,平时写代码的时候用类的ClassLoader就能够了。 ##参考连接 [http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader][1] [1]: http://stackoverflow.com/questions/1771679/difference-between-threads-context-class-loader-and-normal-classloader