浅谈JVM(三):类加载器和双亲委派

上一篇:
浅谈JVM(一):Class文件解析
浅谈JVM(二):类加载机制

3.类加载器和双亲委派

浅谈JVM(三):类加载器和双亲委派_第1张图片

3.1 类加载器

​ 虚拟机规范中将类加载器分成两类:引导类加载器(bootstrap class loader)和自定义加载器(user-defined class loader)。引导类加载器是由虚拟机直接提供的(如HotSpot由C++实现引导类加载器),而自定义加载器是抽象类ClassLoader的子类,由用户实现,这里的用户指虚拟机实现(如HotSpot)或程序开发者。自定义加载器可以加载用户自定义的数据源,例如为了防止反编译,可以将源字节码文件进行加密,再通过自定义类加载器解密后。

​ 在HotSpot中,从JDK1.2到JDK 8版本,系统将类加载器的实现分为三种:启动类加载器、扩展类加载器、应用程序类加载器。绝大多数Java程序都会用到以下三个类加载器:

①启动类加载器(Bootstrap Class Loader):负责加载${JAVA_HOME}\lib目录,或者被-Xbootclasspath参数所指定的路径中存放的,而且是Java虚拟机能够识别的(按照文件名识别,如rt.jar、tools.jar,名字不符合的类库即使放在lib目录中也不会被加载)类库加载到虚拟机的内存中。BootstrapClassLoader不是一个真实存在的Java类,启动类加载器由虚拟机实现,无法被Java程序直接引用,用户在编写自定义类加载器时,如果需要把加载请求委派给启动类加载器去处理,那直接使用null代替即可。

 public static void main(String[] args) {
        //输出null
        System.out.println(ClassLoader.class.getClassLoader());
 }

②扩展类加载器(Extension Class Loader):这个类加载器是在类sun.misc.Launcher E x t C l a s s L o a d e r 中以 J a v a 代码的形式实现的。它负责加载 ExtClassLoader中以Java代码的形式实现的。它负责加载 ExtClassLoader中以Java代码的形式实现的。它负责加载{JAVA_HOME}\lib\ext目录中,或者被java.ext.dirs系统变量所指定的路径中所有的类库。用户可以把通用性的类库放在ext目录里,在JDK 9之后这种扩展机制被模块化取代。

import sun.net.spi.nameservice.dns.DNSNameService;

/**
 * @author 专治八阿哥的孟老师
 */
public class TestClass01 {
    public static void main(String[] args) {
        //输出null
        System.out.println(ClassLoader.class.getClassLoader());
        //DNSNameService在${JAVA_HOME}\lib\ext,输出sun.misc.Launcher$ExtClassLoader
        System.out.println(DNSNameService.class.getClassLoader());
    }
}

③应用程序类加载器(Application Class Loader):这个类加载器由sun.misc.Launcher$AppClassLoader来实现,也叫做"系统类加载器"。它负责加载用户类路径(classpath)上所有的类库,开发者可以直接在代码中使用这个类加载器。如果应用程序中没有自定义过类加载器,一般情况下这个就是程序中默认的类加载器。

import sun.net.spi.nameservice.dns.DNSNameService;

/**
 * @author 专治八阿哥的孟老师
 */
public class TestClass01 {
    public static void main(String[] args) {
        //输出null
        System.out.println(ClassLoader.class.getClassLoader());
        //DNSNameService在${JAVA_HOME}\lib\ext,输出sun.misc.Launcher$ExtClassLoader
        System.out.println(DNSNameService.class.getClassLoader());
        //输出sun.misc.Launcher$AppClassLoader
        System.out.println(TestClass01.class.getClassLoader());
        //输出sun.misc.Launcher$AppClassLoader
        System.out.println(ClassLoader.getSystemClassLoader());
    }
}

上面提到的ExtClassLoader和AppClassLoader都间接继承了ClassLoader,根据虚拟机规范对类加载器的分类,它们都属于user-defined class loader。此外开发者也可以通过继承ClassLoader实现自己定义的类加载器。

浅谈JVM(三):类加载器和双亲委派_第2张图片

3.2 双亲委派模型

​ 系统中的各加载器会按照一定的关系相互配合完成类的加载,各类加载器的协作关系被称为"双亲委派模型"(Parents Delegation Model),协作关系如下图所示:

浅谈JVM(三):类加载器和双亲委派_第3张图片

​ 需要注意的是双亲委派是一种协作关系,不是继承关系,AppClassLoader并不是ExtClassLoader的子类。类加载器中有一个parent属性用于描述"父类加载器"。父类加载器是子类加载器的parent属性,而不是super class,所以应该叫"父|类加载器"而不是"父类|加载器"。

在这里插入图片描述

public static void main(String[] args) {
    //输出sun.misc.Launcher$AppClassLoader
    System.out.println(ClassLoader.getSystemClassLoader());
    //输出sun.misc.Launcher$AppClassLoader
    System.out.println(ClassLoader.getSystemClassLoader().getParent());
    //输出null
 System.out.println(ClassLoader.getSystemClassLoader().getParent().getParent());
 }

​ 双亲委派的协作关系是:类加载器A加载一个类时,先在自己的缓存中查看该类是否加载过,如果已经加载过,就返回,否则向parent类加载器B询问;如果B已经加载,就返回,否则B再向自己的parent询问,重复此过程直到bootstrap classloader启动类加载器;如果到了启动类加载器还没有完成加载请求,则由A自己尝试加载该类。

protected Class<?> loadClass(String name, boolean resolve) 
    throws ClassNotFoundException {
        synchronized (getClassLoadingLock(name)) {
            //检查请求的类是否被加载过
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    //如果父类抛出ClassNotFoundException
                    // 说明父类无法完成加载请求
                }
                if (c == null) {             //父类无法加载时,自己加载
                    c = findClass(name);
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

​ 双亲委派模型对于保证Java程序的稳定运行极为重要。

​ 在虚拟机中,对于类的唯一性的认定,并不只是通过类的全限定名,只有类的全限定名一致,且由同一个类加载器加载,才算是相同。即使两个类来自同一个Class文件,处于同一个虚拟机中,只要不是同一个类加载器加载的,它们就被认为是不同的类,包括使用equals()方法、isInstance()方法、isAssignableFrom()方法、instanceof关键字的运算结果都会受到影响。

​ 通过双亲委派模型,可以让类加载器之间产生一种有优先级的层次关系,避免类重复加载,还可以保证系统安全。如java.lang.Object类在rt.jar中,无论使用哪个类加载器加载它,都一定会委托给最顶级的启动类加载器。即使用户自己定义了Object类,系统也不会加载它。

浅谈JVM(三):类加载器和双亲委派_第4张图片

public static void main(String[] args) throws Exception {
        ClassLoader loader = new ClassLoader() {
            private String path = "D:\\workspace\\";

            @Override
            protected Class<?> findClass(String name) throws ClassNotFoundException {
                try (InputStream is = new FileInputStream(path + name.replace(".", "/") + ".class")) {
                    byte[] bytes = new byte[is.available()];
                    is.read(bytes);
                    return defineClass(name, bytes, 0, bytes.length);
                } catch (IOException e) {
                    throw new ClassNotFoundException(name);
                }
            }
        };
        Object o = loader.loadClass("java.lang.Object").newInstance();
    }

​ 上面的代码运行后会发现,即使自定义了类加载器,Object类也是由启动类加载器加载的,我们自定义的类不会被加载。如果要打破双亲委派,可以直接修改上面代码,把方法名findClass改成loadClass,此时代码运行会报错:

浅谈JVM(三):类加载器和双亲委派_第5张图片

​ 系统类库中的包都是被保护起来的,我们自定义和系统重名的包可以通过编译,但不能正常运行,避免了恶意程序危害系统。

你可能感兴趣的:(java技术,jvm,java,开发语言)