37. 请你详细说说类加载流程,类加载机制及自定义类加载器 中
三、类加载器
类加载器负责将.class文件(不管是jar,还是本地磁盘,还是网络获取等等)加载到内存中,并为之生成对应的java.lang.Class对象。一个类被加载到JVM中,就不会第二次加载了。
那怎么判断是同一个类呢?
每个类在JVM中使用全限定类名(包名+类名)与类加载器联合为唯一的ID,所以如果同一个类使用不同的类加载器,可以被加载到虚拟机,但彼此不兼容。
1、JVM类加载器分类
1.1、Bootstrap ClassLoader
Bootstrap ClassLoader为根类加载器,负责加载java的核心类库。根加载器不是ClassLoader的子类,是有C++实现的。
public class BootstrapTest { public static void main(String[] args) { //获取根类加载器所加载的全部URL数组 URL[] urLs = Launcher.getBootstrapClassPath().getURLs(); Arrays.stream(urLs).forEach(System.out::println); } } //输出结果 //file:/C:/SorftwareInstall/java/jdk/jre/lib/resources.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/rt.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/sunrsasign.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/jsse.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/jce.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/charsets.jar //file:/C:/SorftwareInstall/java/jdk/jre/lib/jfr.jar //file:/C:/SorftwareInstall/java/jdk/jre/classes
根类加载器负责加载%JAVA_HOME%/jre/lib下的jar包(以及由虚拟机参数 -Xbootclasspath 指定的类)。
我们将rt.jar解压,可以看到我们经常使用的类库就在这个jar包中。
1.2 、Extension ClassLoader
Extension ClassLoader为扩展类加载器,负责加载%JAVA_HOME%/jre/ext或者java.ext.dirs系统熟悉指定的目录的jar包。大家可以将自己写的工具包放到这个目录下,可以方便自己使用。
1.3、 System ClassLoader
System ClassLoader为系统(应用)类加载器,负责加载加载来自java命令的-classpath选项、java.class.path系统属性,或者CLASSPATH环境变量所指定的JAR包和类路径。程序可以通过ClassLoader.getSystemClassLoader()来获取系统类加载器。如果没有特别指定,则用户自定义的类加载器默认都以系统类加载器作为父加载器。
四、类加载机制
1、JVM主要的类加载机制。
全盘负责:当一个类加载器负责加载某个Class时,该Class所依赖和引用的其他Class也由该类加载器负责载入,除非显示使用另一个类加载器来载入。
父类委托(双亲委派):先让父加载器试图加载该Class,只有在父加载器无法加载时该类加载器才会尝试从自己的类路径中加载该类。
缓存机制:缓存机制会将已经加载的class缓存起来,当程序中需要使用某个Class时,类加载器先从缓存区中搜寻该Class,只有当缓存中不存在该Class时,系统才会读取该类的二进制数据,并将其转换为Class对象,存入缓存中。这就是为什么更改了class后,需要重启JVM才生效的原因。
注意:类加载器之间的父子关系并不是类继承上的父子关系,而是实例之间的父子关系。
public class ClassloaderPropTest { public static void main(String[] args) throws IOException { //获取系统类加载器 ClassLoader systemClassLoader = ClassLoader.getSystemClassLoader(); System.out.println("系统类加载器:" + systemClassLoader); /* 获取系统类加载器的加载路径——通常由CLASSPATH环境变量指定,如果操作系统没有指定 CLASSPATH环境变量,则默认以当前路径作为系统类加载器的加载路径 */ Enumeration<URL> eml = systemClassLoader.getResources(""); while (eml.hasMoreElements()) { System.out.println(eml.nextElement()); } //获取系统类加载器的父类加载器,得到扩展类加载器 ClassLoader extensionLoader = systemClassLoader.getParent(); System.out.println("系统类的父加载器是扩展类加载器:" + extensionLoader); System.out.println("扩展类加载器的加载路径:" + System.getProperty("java.ext.dirs")); System.out.println("扩展类加载器的parant:" + extensionLoader.getParent()); } } //输出结果 //系统类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2 //file:/C:/ProjectTest/FengKuang/out/production/FengKuang/ //系统类的父加载器是扩展类加载器:sun.misc.Launcher$ExtClassLoader@1540e19d //扩展类加载器的加载路径:C:\SorftwareInstall\java\jdk\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext //扩展类加载器的parant:null
从输出中验证了:系统类加载器的父加载器是扩展类加载器。但输出中扩展类加载器的父加载器是null,这是因为父加载器不是java实现的,是C++实现的,所以获取不到。但扩展类加载器的父加载器是根加载器。
2、类加载流程图
图中红色部分,可以是我们自定义实现的类加载器来进行加载。