一、概述
类加载器用来把类加载到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的核心类库只会由启动类加载器加载, 不会存在 多个版本, 相互之间也是可见的
- 保证了核心类不会被自定义类所替代
- 不同的类加载器可以提供不同的命名空间, 相互之间相互隔离, 不同的类加载器加载的类相互之间不可见