在进入阅读源码之前,首先要来了解下ClassLoader的两个基本问题:
ClassLoader的加载等级
BootStrap ClassLoader
启动类加载器,是Java类加载层次中最顶层的类加载器,负责加载JDK中的核心类库。这个ClassLoader完全是由JVM自己控制的,别人无法访问到这个类,所以它不遵循委托机制,没有子加载器。
通过下面代码获取BootStrap ClassLoader和ExtClassLoader负责加载的文件:
public static void main(String[] args) {
String pathBoot = System.getProperty("sun.boot.class.path");
System.out.println(pathBoot.replaceAll(":", System.lineSeparator()));
System.out.println("--------------------");
String pathExt = System.getProperty("java.ext.dirs");
System.out.println(pathExt.replaceAll(":", System.lineSeparator()));
}
输出:
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/resources.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/rt.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/sunrsasign.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/jsse.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/jce.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/charsets.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/jfr.jar
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/classes
--------------------
/Users/xiaoshan/Library/Java/Extensions
/Library/Java/JavaVirtualMachines/jdk1.8.0_161.jdk/Contents/Home/jre/lib/ext
/Library/Java/Extensions
/Network/Library/Java/Extensions
/System/Library/Java/Extensions
/usr/lib/java
ExtClassLoader
扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
AppClassLoader
系统类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader()来获取它。
ClassLoader的类层次
源码阅读
下面看下ClassLoader类中的主要属性和方法
(1)可以看出这里每个类加载器的父加载器是在类内部的引用关系,并非继承关系。后面会写这个属性值设定的具体过程。
private final ClassLoader parent;
(2)加载指定名称的类,loadclass方法内实现了通过双亲委托机制加载类。
protected Class<?> loadClass(String name, boolean resolve){}
(3)查找指定名称的类,ClassLoader类中只是定义了该方法,如果需要实现自定义类加载器直接重写该方法即可。
protected Class<?> findClass(String name) throws ClassNotFoundException {}
(4)将相应的.class文件解析JVM能够识别的Class对象
protected final Class<?> defineClass(String name, byte[] b, int off, int len,ProtectionDomain protectionDomain){}
接下来看一下loadclass方法的具体内容:
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();
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;
}
}
平常听来无比高大上的双亲委托加载模型就是这般平易近人。
继续来看看parent属性值的设定过程:
先从Launcher类看起,Launcher类是Java应用的入口,是由BootStrapClassLoader加载的,我们只看它的前一部分代码。
public Launcher() {
Launcher.ExtClassLoader var1;
try {
var1 = Launcher.ExtClassLoader.getExtClassLoader();
} catch (IOException var10) {
throw new InternalError("Could not create extension class loader", var10);
}
try {
//初始化AppClassLoader时传入ExtClassLoader的实例作为参数
this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);
} catch (IOException var9) {
throw new InternalError("Could not create application class loader", var9);
}
//设置AppClassLoader为线程上下文类加载器
Thread.currentThread().setContextClassLoader(this.loader);
}
Launcher完成对ExtClassLoader和AppClassLoader的初始化,甚至这两个类是以内部类的方式定义在Launcher类中的。初始化AppClassLoader时传入ExtClassLoader的实例作为参数,
AppClassLoader(URL[] var1, ClassLoader var2) {
super(var1, var2, Launcher.factory);
this.ucp.initLookupCache(this);
}
追踪到这里传给AppClassLoader的构造方法中第二个参数,并不断向上调用父类构造方法,最终将AppClassLoader类对象的parent值设为ExtClassLoader。
自定义ClassLoader
public class MyClassLoader extends ClassLoader{
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
//根据传入的包名解析后找到对应路径下的class文件
File f = new File("c:/test/", name.replace(".", "/").concat(".class"));
try {
FileInputStream fis = new FileInputStream(f);
ByteArrayOutputStream baos = new ByteArrayOutputStream();
int b = 0;
while ((b=fis.read()) !=0) {
baos.write(b);
}
byte[] bytes = baos.toByteArray();
baos.close();
fis.close();
return defineClass(name, bytes, 0, bytes.length);
} catch (Exception e) {
e.printStackTrace();
}
return super.findClass(name);
}
}
一般自定义类加载器只需要重写findclass方法即可,如果需要打破双亲委托机制就得重写loadclass方法。可以研究tomcat自定义类加载器的写法。