目录
上一篇文章《Hotspot 类文件加载、链接和初始化》中讲到AppClassLoader的父类加载器是ExtClassLoader,ExtClassLoader的父类加载器为空,但逻辑上是启动类加载器;ExtClassLoader类和AppClassLoader类两个都是Java编写的,位于rt.jar中,也就是由启动类加载器负责加载这两个类;这两个类的源码在Java的官方源码包src.zip中不存在,只能反编译或者参考OpenJDK8 jdk/share/classes/sun/misc/Launcher.java源码,如下图:
这两个都继承自URLClassLoader,彼此没有继承关系,默认的包级访问,所以无法在sun.misc包外访问,JDK8下 URLClassLoader的类继承关系如下图,下面详细介绍各类加载器。
1、ClassLoader
ClassLoader是一个抽象类,主要定义了加载类和资源的方法,并在方法实现中定义了委托模型,如下图:
以loadClass和getResource方法的实现为例说明,如下:
public Class<?> loadClass(String name) throws ClassNotFoundException {
return loadClass(name, false);
}
protected Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
//返回加载指定类的锁,避免并发加载时的重复加载和死锁
synchronized (getClassLoadingLock(name)) {
//首先检查该类是否已加载,最终由本地方法findLoadedClass0实现
Class<?> c = findLoadedClass(name);
if (c == null) {
long t0 = System.nanoTime();
try {
if (parent != null) {
//如果父类加载器不为空,委托父类加载器加载该类
c = parent.loadClass(name, false);
} else {//父类加载器为空
//从启动类加载器中查找该类,最终由本地方法findBootstrapClass实现
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
//未能读取该类,则调用findClass查找该类,自定义类加载可覆写该方法
//注意ClassLoader中未提供具体实现,只是抛出ClassNotFoundException
if (c == null) {
long t1 = System.nanoTime();
c = findClass(name);
sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
sun.misc.PerfCounter.getFindClasses().increment();
}
}
if (resolve) {
//完成类的解析和初始化,最终由本地方法resolveClass0实现
resolveClass(c);
}
return c;
}
}
public URL getResource(String name) {
URL url;
if (parent != null) {
//如果父类加载器不为空,委托给父类加载器加载该资源
url = parent.getResource(name);
} else {
//如果父类加载器为空,则从启动类路径加载该资源,最终实现在sun.misc.Launcher.getBootstrapClassPath()
url = getBootstrapResource(name);
}
if (url == null) {
//如果找不到,则调用findResource继续查找,同上面的findClass,ClassLoader中未提供具体实现,返回null
url = findResource(name);
}
return url;
}
注意ClassLoader对外的loadClass方法只有一个参数String name,调用同名的保护方法时传递的resolve属性为false,并且委托父类加载器加载时传递的resolve属性也是false,这是因为从JDK1.1之后,JVM不再依赖类加载器去链接,而是完全由JVM根据类的实际使用情况来链接。
其中读取资源相关方法是读取类路径下文件,jar包等的最常用的方法,Spring中解析classpath开头的资源路径时底层也是调用这些方法,示例如下:
@Test
public void test4() throws Exception {
ClassLoader classLoader=MyTest.class.getClassLoader();
//默认从类路径下查找
// InputStream in=classLoader.getResourceAsStream("test.txt");
//getSystemResourceAsStream是利用系统类加载器读取资源
// InputStream in=ClassLoader.getSystemResourceAsStream("test.txt");
//默认从MyTest所在的当前目录读取,加上/表示从类路径下读取
InputStream in=MyTest.class.getResourceAsStream("/test.txt");
readTxt(in);
}
@Test
public void test5() throws Exception {
ClassLoader classLoader=MyTest.class.getClassLoader();
//读取多个同名文件,支持读取jar包中的文件
Enumeration<URL> resources=classLoader.getResources("META-INF/MANIFEST.MF");
while (resources.hasMoreElements()){
URL url=resources.nextElement();
String path=url.getPath();
System.out.println(path);
if(path.contains("junit-4.12.jar")){
readTxt(url.openStream());
}
}
}
private void readTxt(InputStream in) throws Exception{
BufferedReader reader=new BufferedReader(new InputStreamReader(in));
String s=reader.readLine();
while (s!=null){
System.out.println(s);
s=reader.readLine();
}
}
ClassLoader还有一个重要的方法getSystemClassLoader(),该方法返回的系统类加载器通常作为新的类加载器实例的默认父类加载器,并负责加载应用程序自身的jar包,默认的系统启动器是ClassLoader的子类。这个方法会在JVM启动创建java.lang.Thread对象时调用,并将返回结果作为Thread的contextClassLoade。如果设置了属性java.system.class.loader,则getSystemClassLoader()方法返回该属性指定的全限定名的类加载器,该类加载器由默认的系统类加载器加载,要求其必须提供只有一个参数ClassLoader的公开构造方法,然后会将默认的系统加载器作为参数调用该构造方法,创建一个指定类名的类加载器实例,将该实例作为getSystemClassLoader()的返回结果。系统类加载器的初始化逻辑在initSystemClassLoader()方法中实现,如下:
private static synchronized void initSystemClassLoader() {
//sclSet属性为true,表示系统类加载器已初始化
if (!sclSet) {
//scl属性就是系统类加载器,不为空说明已初始化完成,此调用非法
if (scl != null)
throw new IllegalStateException("recursive invocation");
//获取Launcher
sun.misc.Launcher l = sun.misc.Launcher.getLauncher();
if (l != null) {
Throwable oops = null;
//从Launcher获取类加载器作为默认的系统类加载器
scl = l.getClassLoader();