前言
JDK9 之后,使用了模块化的概念,与 JDK8 及之前的类加载器 相比,也有了一些变化。
类加载器
加载器类别的变化
类加载器的类别上,用 平台类PlatformClassLoader
代替了扩展类加载器,引导类加载器和系统类加载器保持不变。
引导类加载器是由类库和代码在虚拟机中实现的。 为了向后兼容,它在程序中仍然使用 null 表示。
// jdk11 test
// Example 01: 三种类加载
public class ClassLoaderDemo{
public static void main(String[] args){
String str = new String("hello bootstrap classloader");
System.out.println(str.getClass().getClassLoader()); // null
Date date = new Date(System.currentTimeMillis());
// jdk.internal.loader.ClassLoaders$PlatformClassLoader@6cd8737
System.out.println(date.getClass().getClassLoader());
// // jdk.internal.loader.ClassLoaders$AppClassLoader@2437c6dc
System.out.println(ClassLoaderDemo.class.getClassLoader());
}
}
复制代码
如上所示,从输出的结果看,String
类是由 ApplicationClassLoader
加载的, Date
类是由 PlatformClassLoader
加载的,而我们自定义的类依然是由 AppClassLoader
加载的。
加载路径的变化
模块化将源码分为了很多小的模块,每个模块都有一共类加载器负责对应加载其中的类。
- 由
BootClassLoader
加载的模块
java.base java.security.sasl
java.datatransfer java.xml
java.desktop jdk.httpserver
java.instrument jdk.internal.vm.ci
java.logging jdk.management
java.management jdk.management.agent
java.management.rmi jdk.naming.rmi
java.naming jdk.net
java.prefs jdk.sctp
java.rmi jdk.unsupported
复制代码
- 由
PlatformClassLoader
加载的模块
java.activation java.xml.ws.annotation jdk.desktop java.compiler
javafx.base jdk.dynalink java.corba javafx.controls
jdk.javaws java.jnlp javafx.deploy jdk.jsobject
java.scripting javafx.fxml jdk.localedata java.se
javafx.graphics jdk.naming.dns java.se.ee javafx.media
jdk.plugin java.security.jgss javafx.swing jdk.plugin.dom
java.smartcardio javafx.web jdk.plugin.server java.sql
jdk.accessibility jdk.scripting.nashorn java.sql.rowset jdk.charsets
jdk.security.auth java.transaction jdk.crypto.cryptoki jdk.security.jgss
java.xml.bind jdk.crypto.ec jdk.xml.dom java.xml.crypto
jdk.crypto.mscapi jdk.zipfs java.xml.ws jdk.deploy
复制代码
- 由
AppClassLoader
加载的模块
jdk.aot jdk.jdeps
jdk.attach jdk.jdi
jdk.compiler jdk.jdwp.agent
jdk.editpad jdk.jlink
jdk.hotspot.agent jdk.jshell
jdk.internal.ed jdk.jstatd
jdk.internal.jvmstat jdk.pack
jdk.internal.le jdk.policytool
jdk.internal.opt jdk.rmic
jdk.jartool jdk.scripting.nashorn.shell
jdk.javadoc jdk.xml.bind*
jdk.jcmd jdk.xml.ws*
jdk.jconsole
复制代码
注意,上面的 类加载 > 加载器类别的变化 中的
Example 01
的例子中,Date
类是java.sql
下的Date
,所以由PlatformClassLoader
加载。
继承结构的变化
JDK9 之前,扩展类加载器和系统类加载器都继承自 URLClassLoader
,我们之前也提到,如果要更加方便地自定义类加载器,可以直接继承自 URLClassLoader
。
JDK9 之后,移除了 URLClassLoader
,引导类加载器有了具体的实现 BootClassLoader
,并且三种类加载器都继承自 BulitinClassLoader
。
使用类加载器的变化
我们可以使用类加载器加载一个类,并获取它的实例。之前我们可以使用 newInstance()
方法获取对象实例。
JDK9 开始,已经将 ClassLoader
的 newInstance
方法标为过时,并且建议使用如下方式代替:
clazz.getDeclaredConstructor().newInstance()
复制代码
原来的方法必须保证类有一共空构造方法,而新的方式通过反射的形式,可以适用于任何情况。
public class ClassLoaderDemo03 {
// 只有一共有参构造函数,没有空构造函数
public ClassLoaderDemo03(String info){
System.out.println(info);
}
public static void main(String[] args) {
ClassLoader loader = Thread.currentThread().getContextClassLoader();
// <= jdk8
try {
// 异常
Object obj01 = (ClassLoaderDemo03) loader.loadClass("com.zcat.jvm.classloader.ClassLoaderDemo03").newInstance();
} catch (Exception e) {
e.printStackTrace();
}
// >= jdk9
try {
// 成功创建实例
Object obj01 = (ClassLoaderDemo03) loader.loadClass("com.zcat.jvm.classloader.ClassLoaderDemo03")
.getDeclaredConstructor(String.class)
.newInstance("使用 >=jdk9 构造的对象");
} catch (Exception e) {
e.printStackTrace();
}
}
}
复制代码
输出的结果如下:
java.lang.InstantiationException: com.zcat.jvm.classloader.ClassLoaderDemo03
使用 >=jdk9 构造的对象
复制代码
双亲委派模式的变化
双亲委派模式的基本结构依然保持,但是类加载器之间的委派关系略有变化。
由于每个类都属于一个模块,所以当收到加载类请求的后,如果能够找到该类属于的模块,就先委派给所属模块的类加载器加载;如果没有找到这样的模块,才会先委派给父类加载器加载。
我们可以在 BuiltinClassLoader
的 loadClassOrNull()
方法中看到具体的加载逻辑:
protected Class<?> loadClassOrNull(String cn, boolean resolve) {
synchronized (getClassLoadingLock(cn)) {
// 是否已经被加载过
Class<?> c = findLoadedClass(cn);
if (c == null) {
// 未被加载过,首先找到对应的模块
LoadedModule loadedModule = findLoadedModule(cn);
if (loadedModule != null) {
// 能找到对应的模块,委派给负责该模块的类加载器
BuiltinClassLoader loader = loadedModule.loader();
if (loader == this) {
if (VM.isModuleSystemInited()) {
c = findClassInModuleOrNull(loadedModule, cn);
}
} else {
c = loader.loadClassOrNull(cn);
}
} else {
// 没有找到模块,委派给父类加载器
if (parent != null) {
c = parent.loadClassOrNull(cn);
}
if (c == null && hasClassPath() && VM.isModuleSystemInited())
c = findClassOnClassPathOrNull(cn);
}
}
}
if (resolve && c != null) resolveClass(c);
return c;
}
}
复制代码
这中变化也打破了双亲委派,如果要加载的类在 BootClassLoader
负责的模块中,则会跳过 BuiltinClassLoader
,直接委派给 BootClassLoader
加载。