JVM学习札记(二)~ 类加载器双亲委托机制

一、概述

类加载器用来把类加载到Java虚拟机中,从JDK 1.2开始,类的加载采用双亲委托机制,这种机制能更好的保证Java平台的安全;在双亲委托机制中,除了Java虚拟机自带的根类加载器以外,其余的类加载都有且只有一个父加载器

在这里插入图片描述

当loader1加载Sample时,会委托给自己的父类加载器“系统类加载器”,“系统类加载器”会委托给“扩展类加载器”,“扩展类加载器”会委托给“根类加载器”,但是根类加载器尝试加载之后,并不能完成加载,便返回给“扩展类加载器”,扩展类加载器也无法加载,便返回给“系统类加载器”,“系统类加载器”可以加载,便完成了加载过程

在这里插入图片描述

二、Java中的三种类加载器

1. Bootstrap ClassLoader(启动类加载器)

负责加载JAVA_HOME中jre/lib/rt.jar里所有的class,由c++实现,不是Classload子类

2. Extension ClassLoader(扩展类加载器)

负责加载Java平台中扩展功能的一些jar包,包括AVA_HOME中jre/lib/*.jar或-Djava.ext.dirs指定目录下的jar包

3. App ClassLoader(系统类加载器)

负责加载classpath中的jar以及class

示例:获取并打印类加载器的类加载路径
/**
 * 查看打印不同的类加载器的加载路径及文件
 * 1. 启动类加载器 System.getProperty("sun.boot.class.path")
 * 2. 扩展类加载器 System.getProperty("java.ext.dirs")
 * 3. 系统/应用类加载器 System.getProperty("java.class.path")
 */
public class ClassLoader_03 {

    public static void main(String[] args) throws IOException {
        Arrays.asList(System.getProperty("sun.boot.class.path")
                .split(":"))
                .forEach(System.out::println);
        System.out.println("----------");
        Arrays.asList(System.getProperty("java.ext.dirs")
                .split(":"))
                .forEach(System.out::println);
        System.out.println("----------");
        Arrays.asList(System.getProperty("java.class.path")
                .split(":"))
                .forEach(System.out::println);

    }
}

输出:

/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/classes
----------
/Users/lizza/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
----------
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/deploy.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/cldrdata.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/dnsns.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jaccess.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/jfxrt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/localedata.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/nashorn.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunec.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunjce_provider.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/sunpkcs11.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/ext/zipfs.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/javaws.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jfxswt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/management-agent.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/plugin.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/ant-javafx.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/dt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/javafx-mx.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/jconsole.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/packager.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/sa-jdi.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/lib/tools.jar
/Users/lizza/Work/idea/learner/jvm/jvm_03_classloader/target/classes
/Applications/IntelliJ IDEA.app/Contents/lib/idea_rt.jar
示例:验证启动类加载器加载自定义的class文件
/**
 * 验证启动类加载器加载自定义的class文件
 * 1. 将ClassLoader_01.class文件拷贝到启动类加载器的加载路径下:/Library/Java/JavaVirtualMachines/jdk1.8.0_221.jdk/Contents/Home/jre/classes
 * 2. 运行程序验证双亲委派机制
 */
public class ClassLoader_04 {

    public static void main(String[] args) throws Exception {
        CustomClassLoader loader = new CustomClassLoader("loader");
        Class<?> clazz = loader.loadClass("com.lizza.ClassLoader_01");
        System.out.println(clazz.hashCode());
        System.out.println(clazz.getClassLoader());

    }
}

输出

1627674070
null

三、定义类加载器与初始类加载器

  • 如果有一个类加载器能够成功的加载Test类,那这个类加载器称为定义类加载器,所有能返回Class对象引用的类加载器(包括定义类加载器)都被称为初始类加载器

四、不同类加载器命名空间的关系

  • 不同类加载器命名空间中的类是相互不可见的,同一个类加载器命名空间中的类是相互可见的
  • 由于自类加载器的命名空间包含父类加载器的命名空间,所以父类加载器加载的类在子类加载器的命名空间中是可见的;但是由子类加载器所加载的类对父类加载器是不可见的
  • 如果两个类加载器之间没有父子关系,那么他们加载的类相互不可见
示例:不同的类加载器命名空间中类的可见性
/**
 * 不同类加载器的命名空间的类的可见性
 * 1. 删除CLASSPATH下com.lizza.User.class文件
 * 2. 将CLASSPATH下com文件夹移至Desktop
 */
public class ClassLoader_07 {

    public static void main(String[] args) throws Exception {
        CustomClassLoader loader1 = new CustomClassLoader("loader1", "/Users/lizza/Desktop/");
        CustomClassLoader loader2 = new CustomClassLoader("loader2", "/Users/lizza/Desktop/");

        Class<?> clazz1 = loader1.loadClass("com.lizza.User");
        Class<?> clazz2 = loader2.loadClass("com.lizza.User");

        /**
         * 输出false, 原因
         * 1. loader1和loader2在加载User类时都会委托给系统类加载器去加载,
         *    系统类加载器无法加载, 便一直向上委托给启动类加载器加载器, 启动类
         *    加载器无法加载, 委托给自定义类加载器
         * 2. 自定义类加载器尝试加载并且可以成功加载, 但是loader1和loader2是两个不同的类加载器
         *    故class对象不相同
         */
        System.out.println(clazz1 == clazz2);
        System.out.println(clazz1.getClassLoader());
        System.out.println(clazz2.getClassLoader());

        Object o1 = clazz1.newInstance();
        Object o2 = clazz2.newInstance();
        Method method = clazz1.getMethod("setUser", Object.class);
        method.invoke(o1, o2);

    }
}

五、双亲委托机制的优势

  1. 保证Java核心类库的安全: 双亲委托机制保证了Java的核心类库只会由启动类加载器加载, 不会存在 多个版本, 相互之间也是可见的
  2. 保证了核心类不会被自定义类所替代
  3. 不同的类加载器可以提供不同的命名空间, 相互之间相互隔离, 不同的类加载器加载的类相互之间不可见
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章