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

    技术2022-07-12  76

    一、概述

    类加载器用来把类加载到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); } }

    五、双亲委托机制的优势

    保证Java核心类库的安全: 双亲委托机制保证了Java的核心类库只会由启动类加载器加载, 不会存在 多个版本, 相互之间也是可见的保证了核心类不会被自定义类所替代不同的类加载器可以提供不同的命名空间, 相互之间相互隔离, 不同的类加载器加载的类相互之间不可见
    Processed: 0.015, SQL: 9