ClassLoader浅析(一) —— Java ClassL
- ClassLoader的具体作用就是将字节码格式文件加载到虚拟机中去。Java中是把class文件加载到JVM。Android中是把dex/odex文件加载入虚拟机。
- 当JVM启动的时候,不会一下子把所有的class文件加载进JVM,而是根据需要去动态加载。
JAVA类加载
- 在Java中有三个类加载器
-
Bootstrap ClassLoader:启动类加载器,最顶层的加载类。负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。他是由C++实现的,并不继承自
java.lang.ClassLoader
。 - Extention ClassLoader:扩展类加载器,负责加载Java的扩展类库,默认加载JAVA_HOME/jre/lib/ext/目下的所有jar。
- Application ClassLoader:应用类加载器,负责加载应用程序classpath目录下的所有jar和class文件。一般来说,Java 应用的类都是由它来完成加载的。
-
Bootstrap ClassLoader:启动类加载器,最顶层的加载类。负责加载JDK中的核心类库,如:rt.jar、resources.jar、charsets.jar等。他是由C++实现的,并不继承自
父加载器
-
父加载器不是父类。 先看下AppClassLoader和ExtClassLoader的继承关系。
image我们来看下ClassLoader的源码:
public abstract class ClassLoader { //父加载器 private final ClassLoader parent; private static ClassLoader scl; private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; ... } protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); } protected ClassLoader() { this(checkCreateClassLoader(), getSystemClassLoader()); } public final ClassLoader getParent() { if (parent == null) return null; return parent; } public static ClassLoader getSystemClassLoader() { initSystemClassLoader(); if (scl == null) { return null; } return scl; } private static synchronized void initSystemClassLoader() { if (!sclSet) { if (scl != null) throw new IllegalStateException("recursive invocation"); sun.misc.Launcher l = sun.misc.Launcher.getLauncher(); if (l != null) { Throwable oops = null; //通过Launcher获取ClassLoader scl = l.getClassLoader(); try { scl = AccessController.doPrivileged( new SystemClassLoaderAction(scl)); } catch (PrivilegedActionException pae) { oops = pae.getCause(); if (oops instanceof InvocationTargetException) { oops = oops.getCause(); } } if (oops != null) { if (oops instanceof Error) { throw (Error) oops; } else { throw new Error(oops); } } } sclSet = true; } } ... }
再来看看
sun.misc.Launcher
,它是一个java虚拟机的入口:public class Launcher { private static Launcher launcher = new Launcher(); private static String bootClassPath = System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() { return launcher; } private ClassLoader loader; public Launcher() { ClassLoader extcl; try { //初始化ExtClassLoader extcl = ExtClassLoader.getExtClassLoader(); } catch (IOException e) { throw new InternalError( "Could not create extension class loader", e); } try { //初始化AppClassLoader loader = AppClassLoader.getAppClassLoader(extcl); } catch (IOException e) { throw new InternalError( "Could not create application class loader", e); } //设置AppClassLoader为线程上下文类加载器 Thread.currentThread().setContextClassLoader(loader); } public ClassLoader getClassLoader() { return loader; } static class ExtClassLoader extends URLClassLoader { private File[] dirs; 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); this.dirs = dirs; } ... } 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 = (s == null) ? new URL[0] : pathToURLs(path); return new AppClassLoader(urls, extcl); } AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); } ... } }
从以上的源码中我们可以知道parent的赋值是在ClassLoader对象的构造方法中,它有两个情况:
-
由外部类创建ClassLoader时直接传入一个ClassLoader为parent。
-
外界不指定parent时,由
getSystemClassLoader()
方法生成,也就是在sun.misc.Laucher
通过getClassLoader()
获取,也就是AppClassLoader。直白的说,一个ClassLoader创建时如果没有指定parent,那么它的parent默认就是AppClassLoader。
-
从loader = AppClassLoader.getAppClassLoader(extcl);
说明AppClassLoader的parent是ExtClassLoader。
但是ExtClassLoader并没有直接对parent赋值。它调用了它的父类也就是URLClassLoder的构造方法并传递了3个参数。
public URLClassLoader(URL[] urls, ClassLoader parent,URLStreamHandlerFactory factory) {
super(parent);
}
真相大白,ExtClassLoader的parent为null。但是实际上ExtClassLoader父类加载器是BootstrapClassLoader,我们可以从双亲委托中找到蛛丝马迹。
双亲委托
java 双亲委派.png 类加载器在加载类或者其他资源时,使用的是如上图所示的双亲委派模型,这种模型要求除了顶层的BootStrap ClassLoader外,其余的类加载器都应当有自己的父类加载器,如果一个类加载器收到了类加载请求,首先会把这个请求委派给父类加载器加载,只有父类加载器无法完成类加载请求时,子类加载器才会尝试自己去加载。要理解双亲委派,可以查看ClassLoader.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) {
//父加载器不为空则调用父加载器的loadClass
c = parent.loadClass(name,false);
} else {
//父加载器为空则调用Bootstrap Classloader
c = findBootstrapClassOrNull(name);
}
} catch (ClassNotFoundException e) {
// ClassNotFoundException thrown if class not found
// from the non-null parent class loader
}
if (c == null) {
// 如果父类加载器无法加载,才尝试加载
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;
}
}
在双亲委托把类加载事件一直往上传递,一直传到ExtClassLoader,由于ExtClassLoader中的parent为null而传给BootStrapClassLoader。所以说ExtClassLoader的父加载器为BootStrapClassLoader。之所以ExtClassLoader不持有BootStrapClassLoader的引用,是因为Bootstrap ClassLoader是由C/C++编写的,它本身是虚拟机的一部分,所以它并不是一个JAVA类,也就是无法在java代d码中获取它的引用。
- 优点:通过双亲委托可以避免重复加载和保证安全性。当父亲已经加载了该类的时候,就没有必要子ClassLoader再加载一次。如果我们自定义一个String来动态替换java核心api中定义的类型,这样会存在非常大的安全隐患,而双亲委托的方式,就可以避免这种情况,因为String已经在启动时就被引导类加载器(Bootstrcp ClassLoader)加载,所以永远也无法加载一个自己写的String,除非你改变JDK中ClassLoader搜索类的默认算法。