简单的分析下类加载器相关的话题,大白话,没有太多高大上的理论,先看一个例子:
public class SimpleJava {
public static void main(String[] args) {
System.out.println(Thread.currentThread().getContextClassLoader());
System.out.println(SimpleJava.class.getClassLoader());
}
}
打印:
sun.misc.Launcher$AppClassLoader@5200089
sun.misc.Launcher$AppClassLoader@5200089
我们知道main调用 ,虚拟机启动,同时引导类加载器加载jdk核心库,那么AppClassLoader是什么时候new的呢?看输出,AppClassLoader是Launcher的内部类。那么我们就找Launcher。
打开openJdk,在jdk\src\share\classes\sun\misc下我们可以找到这个类,接下来我们简单分析下,先看构造函数:
public Launcher() {
// Create the extension class loader
ClassLoader extcl;
try {
extcl = ExtClassLoader.getExtClassLoader(); //静态工厂方法,生成扩展类加载器
} catch (IOException e) {
throw new InternalError(
"Could not create extension class loader");
}
// Now create the class loader to use to launch the application
try {
loader = AppClassLoader.getAppClassLoader(extcl); //通过传递扩展类加载器,生成系统类加载器
} catch (IOException e) {
throw new InternalError(
"Could not create application class loader");
}
// Also set the context class loader for the primordial thread.
Thread.currentThread().setContextClassLoader(loader);
//把系统类加载器复制给线程上线文加载器,有兴趣的可以看下Thread类的构造函数,也是用父线程的设置本线程上下文加载器
//这也是我们前面看到打印的类加载器相同的原因
}
发现在构造函数中,生成扩展类加载器和系统类加载器并初始化线程上下文加载器为系统类加载器。
扩展类加载器
去除一些不太重要的代码,下面就是ExtClassLoader 实例化过程
static class ExtClassLoader extends URLClassLoader {
public static ExtClassLoader getExtClassLoader() throws IOException {
final File[] dirs = getExtDirs();
return new ExtClassLoader(dirs);
}
public ExtClassLoader(File[] dirs) throws IOException {
super(getExtURLs(dirs), null, factory);
}
}
看见没,就是调用URLClassLoader 构造器,传递ExtURL,猜想下,结合我们对类加载器的了解,就是ExtClassLoader 进行类加载的仓库,也就是java系统属性:java.ext.dirs扩展目录的路径,在我本机运行:
public class SimpleJava {
public static void main(String[] args) {
System.out.println(System.getProperty("java.ext.dirs"));
}
}
打印:
C:\Program Files\Java\jre7\lib\ext;C:\WINDOWS\Sun\Java\lib\ext
系统类加载器
我同样整理了下,以便看的更加清楚,和ExtClassLoader 实现的思路一样,也是继承URLClassLoader ,只不过加载的仓库变成了程序的类路径,也就是常说的CLASSPATH:
static class AppClassLoader extends URLClassLoader {
public static ClassLoader getAppClassLoader(final ClassLoader extcl)
throws IOException
{
final String s = System.getProperty("java.class.path");
final File[] path = (s == null) ? new File[0] : getClassPath(s);
URL[] urls = pathToURLs(path);
return new AppClassLoader(urls, extcl);
}
AppClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent, factory);
}
}
看这两个类加载之后,发现最重要是URLClassLoader ,继承的目的,只不过设置加载的仓库而已,具体的加载还是需要URLClassLoader 做,那我们分析下URLClassLoader 是如何进行类加载的。
URLClassLoader 类加载器
先看下继承体系:
发现是继承自ClassLoader抽象类,模板方法模式,在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);
//parent是通过URLClassLoader初始化的时候传递过来
//对于系统类加载路径下的类,launch类中系统类初始化时的父类加载器就是ExtClassLoader
//对于加载扩展路径下的类,parent为null,可以在launch中看到。
} 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;
}
}
可以发现,类加载的机制,如果父类加载器不为null,则父类加载器进行加载,为null,则进行引导类加载器加载,如果都没加载成功,然后在自己进行加载,实在没有则抛出异常ClassNotFoundException ,这就是类加载器的双亲委托机制。
如果想要避开双亲委托机制,则只要重写loadClass这个方法即可。