本篇主要讲解 java自带的三个类加载器 是如何工作的,主要 关注 Launcher.class, URLClassLoader.class , ClassLoader.class 这三个类的源码,介绍 双亲委派模式 是如何实现以及如何工作的
Launcher.class 该类是java的入口
由于该类有C++ 编写,Idea 反编译的 所以源码阅读性不高,我们只需要关注几个核心的方法
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);
}
// 将应用类加载器设置为当前线程的加载器
Thread.currentThread().setContextClassLoader(this.loader);
。。。。
}
}
上面的代码入口 首先初始化 扩展类加载器 并且作为参数 初始化应用类加载器,【这里就实现了 应用类的加载器的父类为扩展类而非通过继承管线】,设置 应用类加载器为当前线程的类加载器。
ExtClassLoader
static class ExtClassLoader extends URLClassLoader {
//继承了 URLClassLoader
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException {
//获取加载路径
final File[] var0 = getExtDirs();
//构造 ExtClassLoader
try {
return (Launcher.ExtClassLoader)AccessController.doPrivileged(new
PrivilegedExceptionAction<Launcher.ExtClassLoader>() {
public Launcher.ExtClassLoader run() throws IOException {
int var1 = var0.length;
for(int var2 = 0; var2 < var1; ++var2) {
MetaIndex.registerDirectory(var0[var2]);
}
return new Launcher.ExtClassLoader(var0);
}
});
} catch (PrivilegedActionException var2) {
throw (IOException)var2.getException();
}
...
}
private static File[] getExtDirs() {
// 由此可见 加载的 lib/ext 目录下的文件
String var0 = System.getProperty("java.ext.dirs");
...
}
}
AppClassLoader
static class AppClassLoader extends URLClassLoader {
//由此可见 记载的是 class path 路径
final String var1 = System.getProperty("java.class.path");
final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1);
//构造AppClassLoader
return (ClassLoader)AccessController.doPrivileged(new
PrivilegedAction<Launcher.AppClassLoader>() {
public Launcher.AppClassLoader run() {
URL[] var1x = var1 == null ? new URL[0] : Launcher.pathToURLs(var2);
return new Launcher.AppClassLoader(var1x, var0);
}
});
//此处重写了父类的 loadClass
public Class<?> loadClass(String var1, boolean var2) throws ClassNotFoundException {
return super.loadClass(var1, var2);
}
}
此处虽然重写了LoadClass方法 但是最终还是调用了 父类的loadClass
URLClassLoader, URLClassLoader 继承了 SecureClassLoader,最终还是继承了 ClassLoader,这里着重讲述 URLClassLoader
//查找类路径并 define
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 {
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;
}
//加载类
public final Class<?> loadClass(String name, boolean resolve)
throws ClassNotFoundException
{
// First check if we have permission to access the package. This
// should go away once we've added support for exported packages.
SecurityManager sm = System.getSecurityManager();
if (sm != null) {
int i = name.lastIndexOf('.');
if (i != -1) {
sm.checkPackageAccess(name.substring(0, i));
}
}
// 调用了 父类的 类加载器
return super.loadClass(name, resolve);
}
以上2个方法 是URLClassLoader 里面最重要的 2个方法, 一个是遵循双亲委派 的loadClass 一个是 查找类路径 并define class,稍后会断点该方法详解
ClassLoader
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();
//各自加载器具体实现的 查找class 并define
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;
}
}
从该类的 loadClass 可以 明确体现出 双亲委派机制,当类加载请求到来时,先从缓存中查找该类对象, 首先让父类加载器去 加载,加载不到再让启动类加载器 去加载,最后都为空 则使用findClass()方法去加载
接下来我们将采用断点的方式 查看一个类是如何被加载的
//类名自己可以随意定义,此处是我自己随便建的一个类。
Class<?> object2 = ClassLoader.getSystemClassLoader().loadClass("WaitLoadClass");
System.out.println("启动类加载器加载的类:"+ object2.hashCode());
1.代码 mian方法 运行Debug,如下图 可以看到程序进入了 AppClassLoader 下的 loadClass
2.进入下一个断点 进入ClassLoader下的loadClass,如下图 可以看到当前的类加载器为AppClassLoader, 父加载器 为ExtClassLoader
3.进入下一个断点, 再次进入ClassLoader下的loadClass,如下图 可以看到当前的类加载器已经为ExtClassLoader 父加载器为null
4.进入下一个断点,代码进入ExtClassLoader的 findClass方法,如下图
5.进入下一个断点,代码再次进入ExtClassLoader的 findClass方法 此时的类加载器已经为应用类加载器
6.再次进入下一个断点,程序结束,打印
总结:通过上面的6步断点操作,不难发现 当我们加载一个 普通类(非lib或者lib/ext) 下的类,首先会进入ClassLoader loadCLass()方法 通过parent 进行比较来找到最顶级的加载器,未加载到就通过findClass,
在所在加载器的目录下查找,查找不到 再交给下一级的加载器来find,感兴趣的可以分别调试下 lib 下的 java.lang.String 类 与lib/ext 下的 com.sun.nio.zipfs.ZipUtils 各自分别是如何运行 loadCLass与findClass 的
说明:由于启动加载的类过多,此处调试为了方便 断点都是加了if 条件,先左键点击设置断点再右键点击断点处 具体 如下图
通过上面的源码以及断点调试 基本还原了 java ClassLoader的双亲委派模式,下期我们将介绍自定义类加载器。
END