JVM-白话聊一聊JVM类加载和双亲委派机制源码解析
JVM - 自定义类加载器
举个例子 有个类 Artisan
我们希望通过自定义加载器 直接从某个路径下读取Artisan.class . 而不是说 通过自定义加载器 委托给 AppClassLoader ------> ExtClassLoader ----> BootClassLoader 这么走一遍,都没有的话,才让自定义加载器去加载 Artisan.class . 这么一来 还是 双亲委派。
我们期望的是 Artisan.class 及时在 AppClassLoader 中存在,也不要从AppClassLoader 去加载。
说白了,就是 直接让自定义加载器去直接加载Artisan.class 而不让它取委托父加载器去加载,不要去走双亲委派那一套。
我们知道 双亲委派的机制是在ClassLoader # loadClass方法中实现的,打破双亲委派,那我们是不是可以考虑从这个地方下手呢?
核心: 重写ClassLoader#loadClass方法
刚才的思路是对的,要打破它,那就搞loadClass方法。
重写loadClass方法呗。
我们基于 JVM - 自定义类加载器 再来搞一搞
需要再此基础上 重写loadClass 方法
回归下双亲委派的源码
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
// 检查当前类加载器是否已经加载了该类 ,加载直接返回
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) {
}
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;
}
}
那打破它,那我们就不要委托父加载器了呗,直接去findClass 不就好了?
我们把loadClass方法的源码copy过来 把双亲委派的部分代码去掉吧,走 改下
public class MyClassLoaderTest {
static class MyClassLoader extends ClassLoader {
private String classPath;
public MyClassLoader(String classPath) {
this.classPath = classPath;
}
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;
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// First, check if the class has already been loaded
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
try {
byte[] data = loadByte(name);
//defineClass将一个字节数组转为Class对象,这个字节数组是class文件读取后最终的字节数组。
return defineClass(name, data, 0, data.length);
} catch (Exception e) {
e.printStackTrace();
throw new ClassNotFoundException();
}
}
}
重点
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
synchronized (getClassLoadingLock(name)) {
// 尝试加载,不存在直接去findClass ,不走委托父类
Class<?> c = findLoadedClass(name);
if (c == null) {
c = findClass(name);
}
if (resolve) {
resolveClass(c);
}
return c;
}
}
运行下
略微尴尬, Object.class 找不到 。 为啥 呢? 你加载Boss1的时候, Boss1的父类也需要被加载, 你又把双亲委派给关了, 这个自定义的加载器在本地路径下是找不到Object.class的 。
咋办? 放到自定义的加载器加载的路径下 ?
-----> 其实是不行的,Object 谁能篡改的了啊 ,Object只能由引导类加载器来加载。
所以换个思路 ,自己的类路径下的对象走我自己的classLoader, 其他的类 还是走双亲委派
if ("com.gof.facadePattern.Boss1".equals(name)){
c = findClass(name);
}else{
// 交由父加载器去加载
c = this.getParent().loadClass(name);
}
这个时候我们在AppClassLoader加载的路径下 再创建个Boss1 (如果走的还是双亲委派,那加载器肯定还是AppClassLoader)
看 是不是这个Boss1 还是被自定义的ClassLoader加载,如果是,说明打破成功。
OK,双亲委派机制 打破成功。
这个在tomcat类加载机制中非常重要,所以需要彻底明白这一点。