后面就是使用和卸载的过程。
类加载器就是将class文件加载到jvm中。
验证三种加载器加载的类文件:
public static void main(String[] args) {
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"));
}
在这里插入图片描述
从图可以看出类加载对只会加载自己负责的那部分class文件到内存中。
我们通过看启动器(Launch)构造方法里面的内容,来一探究竟 类加载器是如何初始化的
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}}
在构造方法中,可以看出系统创建了两个加载器,分别为:ExtClassLoader和AppClassLoader,我们平时默认使用的类加载器就是使用
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
这个加载器加载的,我们平时调用Class.class.getClassLoader()
方法返回的就是这个初始化的加载器
我们执行如下代码
public static void main(String[] args) {
System.out.println(TestJVMClassLoader.class.getClassLoader().toString());
System.out.println(TestJVMClassLoader.class.getClassLoader().getParent().toString());
System.out.println(TestJVMClassLoader.class.getClassLoader().getParent().getParent());
}
可以发现加载我们创建代码的类加载器是AppClassLoader,AppClassLoader的父类是ExtClassLoader,ExtClassLoader的父类是null,这里出现了两个类加载器,还有一个引导类加载器呢,如果没有猜错的话,应该就是null,那么为什么会是null呢? 还记得我们前面说过引导类加载器是是用C语言写的,既然是C语言写的,又怎么能打印在这呢,所以我们现在画个图来梳理下这三者的关系:
有很多同学都听过这个名词,但是就是没有一篇文章能讲清楚到底什么是双亲委派机,今天我用我毕生所学,让同学们彻底理解什么是双亲委派机制。
我们直接看源码,就很容易理解什么是双亲委派;
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) {
// 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();
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(String name) throws ClassNotFoundException {
throw new ClassNotFoundException(name);
}
我们用一张图来说下找“class的过程”
其实最后一步,自己想办法,也就是实现父类的findclass方法。想必大家在看完这段讲解后,对双亲委派应该有个大致的了解了,如果真的认真看完这个流程的话,相信大家肯定会有疑问: 如果自己需要加载这个字节码的话,为什么不直接调用自己的findclass方法呢,还得一级一级往上找呢,JVM为什么要设置双亲委派机制呢?
package java.util;
public class Date {
public static void main(String[] args) {
System.out.println("我被执行");
}
}
执行结果:
为什么会出现这种情况呢,main方法为什么找不到呢?其实这就是双亲委派机制在起作用,因为java系统中已经有同包名的Date类了,当我们运行我们的main方法是,他首先得要加载Date类。根据双亲委派机制,AppClassLoader得先询问父加载器有没有加载过这个Date,经过询问发现,父类已经加载了这个类,所以AppClass就不要自己再加载一遍了,直接使用父加载器加载的系统Date类,但是系统Date类是没有main方法的。所以才会出现上面的错误。
package com.lezai;
import java.io.FileInputStream;
import java.lang.reflect.Method;
public class CustomClassloaderTest {
static class CustomClassloader extends ClassLoader{
private String classPath = "/Users/yangle/Desktop";
/**
* 自己实现查找字节码文件的逻辑,可以来自本地磁盘,也可以是来自网络
* @param name
* @return
* @throws ClassNotFoundException
*/
@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();
}
return null;
}
// 将文件读取到字节数组
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;
}
}
public static void main(String[] args) {
CustomClassloader classLoader = new CustomClassloader();
try {
Class clazz = classLoader.loadClass("com.lezai.Test");
Object obj = clazz.newInstance();
Method method= clazz.getDeclaredMethod("out", String.class);
method.invoke(obj,"乐哉");
System.out.println(clazz.getClassLoader());
} catch (Exception e) {
e.printStackTrace();
}
}
}
实现步骤:
我们如果需要打破双亲委派机制,只需要自己实现loadClass方法,不再去询问父类中是否加载过我们需要的字节码文件,然后直接调用findClass加载我们的类就行了。
以Tomcat类加载为例,Tomcat 如果使用默认的双亲委派类加载机制行不行?
作者:乐哉
图片:来源于网络,如有侵权,联系删除。