Java中的类加载器
- 引导类加载器:负责加载位于JRE的lib目录下的核心类库,比如
rt.jar、charsets.jar等 - 扩展类加载器:负责加载位于JRE的lib目录下的ext扩展目录中的jar包的类
- 应用程序类加载器:负责加载
ClassPath路径下的类包,主要就是加载开发人员编写的类 - 自定义加载器:负责加载用户自定义路径下的类包
下面这段代码将打印出上述几种情况中,各个类对应的类加载器
package org.laugen.jvm;
import sun.misc.Launcher;
import java.net.URL;
public class JdkClassLoader {
public static void main(String[] args) {
System.out.println(String.class.getClassLoader());
System.out.println(com.sun.crypto.provider.DESKeyFactory.class.getClassLoader().getClass().getName());
System.out.println(JdkClassLoader.class.getClassLoader().getClass().getName());
System.out.println("=============================================");
ClassLoader appClassLoader = ClassLoader.getSystemClassLoader();
ClassLoader extClassloader = appClassLoader.getParent();
ClassLoader bootstrapLoader = extClassloader.getParent();
System.out.println("the bootstrapLoader : " + bootstrapLoader);
System.out.println("the extClassloader : " + extClassloader);
System.out.println("the appClassLoader : " + appClassLoader);
System.out.println("=============================================");
System.out.println("bootstrapLoader加载以下文件:");
URL[] urls = Launcher.getBootstrapClassPath().getURLs();
for (int i = 0; i < urls.length; i++) {
System.out.println(urls[i]);
}
System.out.println("=============================================");
System.out.println("extClassloader加载以下文件:");
System.out.println(System.getProperty("java.ext.dirs"));
System.out.println("=============================================");
System.out.println("appClassLoader加载以下文件:");
System.out.println(System.getProperty("java.class.path"));
}
}
执行结果如下:
null
sun.misc.Launcher$ExtClassLoader
sun.misc.Launcher$AppClassLoader
=============================================
the bootstrapLoader : null
the extClassloader : sun.misc.Launcher$ExtClassLoader@4b67cf4d
the appClassLoader : sun.misc.Launcher$AppClassLoader@18b4aac2
=============================================
bootstrapLoader加载以下文件:
file:/D:/Java/jdk1.8.0_191/jre/lib/resources.jar
file:/D:/Java/jdk1.8.0_191/jre/lib/rt.jar
file:/D:/Java/jdk1.8.0_191/jre/lib/sunrsasign.jar
file:/D:/Java/jdk1.8.0_191/jre/lib/jsse.jar
file:/D:/Java/jdk1.8.0_191/jre/lib/jce.jar
file:/D:/Java/jdk1.8.0_191/jre/lib/charsets.jar
file:/D:/Java/jdk1.8.0_191/jre/lib/jfr.jar
file:/D:/Java/jdk1.8.0_191/jre/classes
=============================================
extClassloader加载以下文件:
D:\Java\jdk1.8.0_191\jre\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
=============================================
appClassLoader加载以下文件:D:\Java\jdk1.8.0_191\jre\lib\charsets.jar;D:\Java\jdk1.8.0_191\jre\lib\deploy.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_191\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_191\jre\lib\javaws.jar;D:\Java\jdk1.8.0_191\jre\lib\jce.jar;D:\Java\jdk1.8.0_191\jre\lib\jfr.jar;D:\Java\jdk1.8.0_191\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_191\jre\lib\jsse.jar;D:\Java\jdk1.8.0_191\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_191\jre\lib\plugin.jar;D:\Java\jdk1.8.0_191\jre\lib\resources.jar;D:\Java\jdk1.8.0_191\jre\lib\rt.jar;E:\learning\boat\jvm\code\jvm\target\classes;D:\JetBrains\IntelliJ IDEA 2020.3\lib\idea_rt.jar
注意:由于引导类加载器BootstrapClassLoader的实例是由C++创建的,因此这里打印出来的是null
类加载器初始化过程
创建JVM启动器实例sun.misc.Launcher时,在起构造方法Launcher()内部,会创建扩展类加载器ExtClassLoader和应用程序类加载器AppClassLoader
JVM会默认调用Launcher的getClassLoader()方法返回的类加载器加载我们编写的类,而它返回的正是AppClassLoader
public Launcher() {
Launcher.ExtClassLoader var1;
try {
// 构造拓展类加载器,过程中会将其parent设置为null
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
// 构造应用程序类加载器,起parent设置为ExtClassLoader
// Launcher的loader属性值时AppClassLoader,一般用这个加载器加载我们的类
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
Thread.currentThread().setContextClassLoader(this.loader);
String var2 = System.getProperty("java.security.manager");
// 省略代码......
}
双亲委派机制
如图所示,就是双亲委派机制的核心体现,一句话概括就是,先找父加载器加载,不行再由子加载器自己加载。
这里通过阅读AppClassLoader加载类的源码来理解一下双亲委派机制,当我们调用AppClassLoader的loadClass方法时,最终实际调用的是其父类ClassLoader的loadClass方法,该方法的大体流程如下:
- 检查一下该类是否已经加载过了,如果加载过了,就直接返回
- 如果该类没有加载过,判断当前加载器是否由父加载器,如若有,则由父加载器加载,如若无,则由引导类加载器加载
- 如过父加载器和引导类加载器都没有找到指定的类,则由当前类加载器的
findClass方法完成类加载
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
// 先检查该类是否已经加载过了
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
// 如果当前加载器的父加载器不为空,则委托父加载器加载该类
c = parent.loadClass(name, false);
} else {
// 如果当前加载器的父加载器为空,则委托引导类加载器加载该类
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// If still not found, then invoke findClass in order
// to find the class.
long t1 = System.nanoTime();
// 调用URLClassLoader的findClass方法在加载器的类路径里查找并加载该类
c = findClass(name);
// this is the defining class loader; record the stats
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
protected Class<?> findClass(final String name) throws ClassNotFoundException {
final Class<?> result;
try {
result = AccessController.doPrivileged(
new PrivilegedExceptionAction<Class<?>>() {
public Class<?> run() throws ClassNotFoundException {
String path = name.replace('.', '/').concat(".class");
Resource res = ucp.getResource(path, false);
if (res != null) {
try {
// defineClass方法会执行一系列类加载的过程
return defineClass(name, res);
} catch (IOException e) {
throw new ClassNotFoundException(name, e);
}
} else {
return null;
}
}
}, acc);
} catch (java.security.PrivilegedActionException pae) {
throw (ClassNotFoundException) pae.getException();
}
if (result == null) {
throw new ClassNotFoundException(name);
}
return result;
}
为什么要设计双亲委派机制?
- 沙箱安全机制:用户不能自己实现核心类库中的类,以此防止核心API库被随意篡改
- 避免类的重复加载:当父加载器已经加载了该类时,子加载器就没有必要再加载一次了,保证被加载类的唯一性
全盘负责委托机制
全盘负责是指当一个ClassLoader装在一个类时,除非显式地使用另一个ClassLoader,否则该类所依赖及引用的类默认由这个ClassLoader载入
自定义类加载器
自定义类加载器需要继承java.lang.ClassLoader类,该类有两个核心方法,一个是loadClass,实现了双亲委派机制,还有一个方法是findClass,默认实现是空方法,所以我们自定义类加载器主要是重写findClass方法。
package org.laugen.jvm;
public class Note {
static {
System.out.println("加载了org.laugen.jvm.Note类");
}
public Note() {
System.out.println("创建了org.laugen.jvm.Note类的实例");
}
public void print() {
System.out.println("这是一个note");
}
}
package org.laugen.jvm;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class TestCustomizeClassLoader {
static class CustomizeClassLoader extends ClassLoader {
private String classPath;
public CustomizeClassLoader(String classPath) {
this.classPath = classPath;
}
// 读取class字节码文件
private byte[] loadByte(String name) throws Exception {
name = name.replaceAll("\\.", "/");
FileInputStream fis = new FileInputStream(classPath + "/" + name + ".class");
int len = fis.available();
byte[] data = new byte[len];
fis.read(data);
fis.close();
return data;
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
public static void main(String args[]) throws Exception {
CustomizeClassLoader classLoader = new CustomizeClassLoader("D:/MyClasses");
System.out.println("自定义类加载器的父加载器:" + classLoader.getParent().getClass().getName());
Class clazz = classLoader.loadClass("org.laugen.jvm.Note");
Object obj = clazz.newInstance();
Method method = clazz.getDeclaredMethod("print", null);
// 调用Note类的print方法
method.invoke(obj, null);
System.out.println("Note类的类加载器是:" + clazz.getClassLoader().getClass().getName());
}
}
执行结果如下:
自定义类加载器的父加载器:sun.misc.Launcher$AppClassLoader
加载了org.laugen.jvm.Note类
创建了org.laugen.jvm.Note类的实例
这是一个note
Note类的类加载器是:org.laugen.jvm.TestCustomizeClassLoader$CustomizeClassLoader
打破双亲委派机制
双亲委派机制的核心实现就在loadClass方法中,因此,打破双亲委派机制,需要重写loadClass方法
@Override
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
synchronized (getClassLoadingLock(name)) {
Class<?> c = findLoadedClass(name);
if (c == null) {
long t1 = System.nanoTime();
if (name.startsWith("org.laugen.jvm.Note")) {
// Note类加载的时候不走双亲委派过程,直接加载
c = findClass(name);
} else {
// 由于加载Note类时,需要加载Object,因为全盘委托设计,此处会用我们自定义的加载器加载
// 因此,针对这些类,应该交由父加载器AppClassLoader去走双亲委派机制完成加载
c = this.getParent().loadClass(name);
}
PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
PerfCounter.getFindClasses().increment();
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
那么,你见过哪些打破双亲委派机制的设计呢?