JVM笔记:Java虚拟机的类加载器和双亲委派机制

  • 类与类加载器

    类加载器虽然只用于实现类的加载动做,可是它在Java程序中起到的做用却远远不限于类加载阶段。对于仍和一个类,都须要由加载它的类加载器和这个类自己一同确立其在Java虚拟机中的惟一性,每个类加载器,都拥有一个独立的类名称空间。java

    换而言之,判断两个类是否相等,只有在这两个类是由同一个类加载器加载的前提下才有意义,不然,即便两个类来源于同一个Class文件,被同一个虚拟机加载,只要加载他们的类加载器不一样,那这两个类一定不相同。 这里指的相等,包括表明类的Class对象的equal方法、isAssignableFrom方法、isInstance方法的返回结果,也包括使用instanceof关键字作对象所属关系判断等状况。以下面这个例子。bootstrap

public class MyClassLoader extends ClassLoader {
    @Override
    public Class<?> loadClass(String name) throws ClassNotFoundException {
        try {
            String fileName = name.substring(name.lastIndexOf(".") + 1) + ".class";
            InputStream inputStream = getClass().getResourceAsStream(fileName);
            if (inputStream == null) {
                return super.loadClass(name);
            }

            byte[] bytes = new byte[inputStream.available()];
            inputStream.read(bytes);
            return defineClass(name, bytes, 0, bytes.length);
        } catch (IOException e) {
            throw new ClassNotFoundException(name);
        }
    }
}

public class MyClass {
    public static void main(String[] args) throws Exception {
        Object object = new MyClassLoader().loadClass("com.verzqli.lib.MyClass").newInstance();
        System.out.println("objectClass = [" + object.getClass() + "]");
        System.out.println("objectInstanceOf= [" + (object instanceof com.verzqli.lib.MyClass) + "]");
    }
}
输出结果:
objectClass = [class com.verzqli.lib.MyClass]
objectInstanceOf= [false]
复制代码

上例中自定义了一个类加载器MyClassLoader,用它去加载了MyClass类,第一个输出结果代表他实例化的确实是MyClass,可是在后续的类型检查中却返回了false,由于虚拟机中存在了两个MyClass类,一个由系统应用程序类加载器加载的,另外一个是由咱们自定义的类加载器加载的,虽然都是来自于同一个Class文件,但依然是两个独立的类,因此类型检查是结果天然为false。bash

  • 双亲委派机制

    从虚拟机的角度来看,只存在两种不一样的类加载器:ide

    • 启动类加载器(Bootstrap ClassLoader):这个类加载器使用C++实现(只限于HotSpot),是虚拟机自身的一部分。
    • 全部其余的类加载器:这些类加载器都由Java实现,独立于虚拟机外部,而且所有都继承自抽象类java.lang.classLoader

    但从开发人员角度来看,类加载器还能够划分的更细致一些,主要都会使用到一下三种系统提供的类加载器:ui

    • 启动类加载器(Bootstrap ClassLoader):负责加载<JAVA_HOME>\lib(相似a.jar),或者被-X bootclasspath参数指定路径中且是能被虚拟机识别的类库假如到虚拟机内存中。改加载器不能被Java程序直接引用,在编写自定义类加载器时,若是须要把加载请求委派给启动类加载器,那直接使用null替代便可。
/**
     * Returns the class loader for the class.  Some implementations may use
     * null to represent the bootstrap class loader. This method will return
     * null in such implementations if this class was loaded by the bootstrap
     * class loader.
     */
 public ClassLoader getClassLoader() {
        ClassLoader cl = getClassLoader0();
        if (cl == null)
            return null;
        SecurityManager sm = System.getSecurityManager();
        if (sm != null) {
            ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass());
        }
        return cl;
    }
复制代码
  • 扩展类加载器(Extension ClassLoader):这个加载器由sun.misc.Launch$ExtClassLoader实现,他负责加载<JAVA_HOME>\lib\ext目录中,或者被java.ext.dirs系统变量锁指定的路径中的全部类库,开发者能够直接使用它。this

  • 应用程序加载器(ApplicationClassLoader):这个加载器由sun.misc.Launch$AppClassLoader实现,,这个类加载器同事也是getSystemClassLoader()方法的返回值,因此也称系统类加载器。它负责加载用户类路径上所指定的类库,开发者能够直接使用这个加载器,通常状况下若是应用程序没有自定义本身的类加载器,那么该加载器就是程序中默认的加载器。spa

通常状况下咱们的应用程序都是由着三种类加载器互相配合进行加载的,若是有必要,能够加入本身定义的类加载器。他们的关系以下图。 code

双亲委派过程.png

上图中展现的类加载器之间的这种层次关系,称为类加载器的双亲委派模型(Parent Delegation Model):除了最顶层的启动类加载器以外,其他的类加载器都应当有本身的父类加载器(这里类加载器之间的斧子关系通常用组合关系来实现,并非继承关系)。cdn

双亲委派的工做机制:若是一个类加载器收到了加载类的请求,它自身不会先去加载这个类,而是把这个请求委派给父类加载器去加载,若是父类加载器还有父类,那么继续向上请求委派,最后全部的加载请求都到了顶层的启动类加载器中。只有当父类加载器返回本身没法加载这个类时(在它的加载范围内没找到这个类)时,子类加载器才会尝试本身加载。对象

双亲委派模型有一个显而易见的好处就是Java类随着他的类加载器一块儿具有了一种带有优先级的层次关系,例如类java.lang.Object,他存放于rt.jar之中,不管哪个类加载器要加载这个类,最终都要委派给启动类加载器来进行加载,因此Object类在程序的各类类加载器环境中都是一个类。相反若是没有双亲委派模型,由各个类加载器自行去加载的话,那么系统中将会出现多个不一样的Object类,这样连Java类型体系中最基础的行为都没法获得保障,应用程序也会一片混乱。

假若你编写了一个包名和类名都和父类加载器中加载范围内存在的类如出一辙,那么最后加载出来的类只会是父类加载器中那个类,你自定义的这个类不会被加载出来。

双亲委派实现的功能很重要,可是其实现却很是简单,以下面加载类的代码:

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) {  
                        c = parent.loadClass(name, false);
                    } else {
                       // 若是父类为null,那么当前必定是启动类加载器
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // 若是父类加载器抛出ClassNotFoundException 异常,说明父类加载器完成加载
                }

                if (c == null) {
                    //当父类加载器没法加载到类的时候,才调用自身的加载方法去加载类
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // 定义类装入器;记录统计信息
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }
复制代码
相关文章
相关标签/搜索