结合源码分析Java中的三种ClassLoader的关系
2017-10-14 14:26
706 查看
一、三种基本的ClassLoader
Java体系中定义了三种类加载器:BootstrapClassLoader、ExtClassLoader、AppClassLoader。需要注意的是,ExtClassLoader、AppClassLoader是位于sun.misc.Launcher中定义的内部类,是在Launcher的构造函数中创建的,下文会详细说明。1、BootstrapClassLoader
引导类加载器,加载指定的JDK核心类库,在Java程序中无法直接引用,因为该加载器是在JVM中用c++编写的。负责加载已下核心类库:%JAVA_HOME%/jre/lib目录
-Xbootclasspath参数指定的目录
系统属性sun.boot.class.path指定的目录总特定名称的jar包
2、ExtClassLoader
拓展加载器,加载拓展类。加载已下两种类库:%JAVA_HOME%/jre/lib/ext目录
系统属性java.ext.dirs所指定的目录中的所有类库
3、AppClassLoader
系统类加载器。加载Java应用程序类库,加载类库的路径由系统环境变量ClassPath,-cp或系统属性java.class.path指定二、关于自定义ClassLoader
public class ClassLoaderTest { private static final String LOAD_PATH = "D:\\ideaWork\\blog\\out\\production\\classes\\com\\blog\\po\\";//自定义加载路径 public static void main(String[] args) throws Exception { ClassLoader classLoader=new ClassLoader() { @Override public Class<?> loadClass(String name) throws ClassNotFoundException { try { String fileName = name.substring(name.lastIndexOf(".") + 1)+".class"; // String fileName = name + ".class"; InputStream inputStream = null; try { inputStream = new FileInputStream(new File(LOAD_PATH + fileName)); } catch (Exception e) { inputStream = null; } //当加载Class,JVM都会先去加载它的父类。所以,Object等父类应该由父类加载器来加载 if (inputStream == null) { //如果不存在该文件,使用父类加载器进行加载 return super.loadClass(name); } byte[] bytes = new byte[inputStream.available()]; inputStream.read(bytes); return defineClass(name, bytes, 0, bytes.length);//加载类 } catch (IOException e) { e.printStackTrace(); } return super.loadClass(name); } }; Object object = classLoader.loadClass("com.blog.po.User").newInstance(); System.out.println(object.getClass()); System.out.println(object.getClass().getClassLoader()); System.out.println(object.getClass().getClassLoader().getParent()); System.out.println(object.getClass().getClassLoader().getParent().getParent()); System.out.println(object.getClass().getClassLoader().getParent().getParent().getParent()); } }
com.blog.po.User是我放在自定义路径下的另一个class文件
运行结果:
class com.blog.po.User com.own.test.ClassLoaderTest$1@7f31245a sun.misc.Launcher$AppClassLoader@18b4aac2 sun.misc.Launcher$ExtClassLoader@135fbaa4 null
上面的Demo演示了如何用自定义的ClassLoader加载自定义的类。当加载Class,JVM都会先去加载它的父类。所以,Object等父类应该由父加载器来加载。事实上,如果不用父加载器加载的话,会出现类似与java.io.FileNotFoundException: \**\Object.class (系统找不到指定的文件。)这样的错误。
从上面的运行结果可以看到,com.blog.po.User这个类是用我们自定义的类加载器com.own.test.ClassLoaderTest$1@7f31245a加载的。而他的父加载链路为sun.misc.Launcher$AppClassLoader@18b4aac2、sun.misc.Launcher$ExtClassLoader@135fbaa4,而ExtClassLoader的父类加载器为BootstrapClassLoader上面的运行结果之所以为null,就是因为这个引导类加载器并不是由Java写的,在Java程序中是引用不到的。
三、双亲委派机制的理解
1、并不是属于继承关系
上面提到过父加载器,并不是意味着这三种类加载器是属于继承关系。并且,由于BootstrapClassLoader是C++编写的,在Java程序中是无法继承的。而双亲委派机制更重要的是一种委派机制:当加载一个类是,先用类中定义的一个名为parent的ClassLoader(也就是当前加载器的父加载器)去加载,如果父加载器加载不到,再由自己加载。ClassLoader中的源码为:
protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { synchronized (getClassLoadingLock(name)) { // 首先加载器会先检查这个类是否已经被加载过,如果是,从缓存的结果中直接返回该Class Class<?> c = findLoadedClass(name); if (c == null) { long t0 = System.nanoTime(); try { if (parent != null) { c = parent.loadClass(name, false);//如果父加载器不为空,就用父加载器去加载 } else { c = findBootstrapClassOrNull(name);//如果父加载器为空,那么就用BootstrapClassLoader去加载 } } 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. b44b 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这个类内部变量为:
// The parent class loader for delegation // Note: VM hardcoded the offset of this field, thus all new fields // must be added *after* it. private final ClassLoader parent;
它是在ClassLoader初始化时通过构造函数传入并赋值的,具体源码如下:
private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; } }
2、三个基本加载器之间的关系
由于ExtClassLoader、AppClassLoader是位于sun.misc.Launcher中定义的内部类,在调用Launcher的默认构造函数中创建的,而Launcher是伴随虚拟机初始化时加载Java主类时调用位于/src/share/vm/classfile/systemDictionary.cpp的SystemDictionary::compute_java_system_loader()函数创建的,其中调用了ClassLoader的initSystemClassLoader()函数: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; 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 { // wrap the exception throw new Error(oops); } } } sclSet = true; } }
可以看到,通过sun.misc.Launcher l = sun.misc.Launcher.getLauncher();完成创建,然后转入这个函数:
public static Launcher getLauncher() { return launcher; }
而Launcher在初始化时会持有一个自身的引用:
private static Launcher launcher = new Launcher();
Launcher的构造函数实现如下:
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的创建:
public static Launcher.ExtClassLoader getExtClassLoader() throws IOException { final File[] var0 = getExtDirs(); 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(); } }
其中的调用 new Launcher.ExtClassLoader(var0);完成创建,var0是由getExtDirs()返回的文件数组,也就是上文提到的加载路径,其具体实现如下:
private static File[] getExtDirs() { String var0 = System.getProperty("java.ext.dirs"); File[] var1; if (var0 != null) { StringTokenizer var2 = new StringTokenizer(var0, File.pathSeparator); int var3 = var2.countTokens(); var1 = new File[var3]; for(int var4 = 0; var4 < var3; ++var4) { var1[var4] = new File(var2.nextToken()); } } else { var1 = new File[0]; } return var1; }
然后调用了ExtClassLoader的构造函数:
public ExtClassLoader(File[] var1) throws IOException { super(getExtURLs(var1), (ClassLoader)null, Launcher.factory); SharedSecrets.getJavaNetAccess().getURLClassPath(this).initLookupCache(this); }
这里调用父类的构造函数完成初始化,传入的参数中,我们需要关注的是第二个参数传入的是null,而父类URLClassLoader的构造函数实现为:
public URLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { super(parent); //其他代码 }
protected SecureClassLoader(ClassLoader parent) { super(parent); //... } protected ClassLoader(ClassLoader parent) { this(checkCreateClassLoader(), parent); }
最终进入到ClassLoader的构造函数中:
private ClassLoader(Void unused, ClassLoader parent) { this.parent = parent; if (ParallelLoaders.isRegistered(this.getClass())) { parallelLockMap = new ConcurrentHashMap<>(); package2certs = new ConcurrentHashMap<>(); domains = Collections.synchronizedSet(new HashSet<ProtectionDomain>()); assertionLock = new Object(); } else { // no finer-grained lock; lock on the classloader instance parallelLockMap = null; package2certs = new Hashtable<>(); domains = new HashSet<>(); assertionLock = this; } }
其实我们关注的只有一行代码:this.parent = parent;
而ExtClassLoader传入的是null,也就是说,ExtClassLoader的父加载器为null。从上文中引用的ClassLoader的loadClass函数中有一段代码:
if (parent != null) { c = parent.loadClass(name, false);//如果父加载器不为空,就用父加载器去加载 } else { c = findBootstrapClassOrNull(name);//如果父加载器为空,那么就用BootstrapClassLoader去加载 }ExtClassLoader的parent即父加载器为null,所以当调用loadClass时,会走上面的if语句中的下面那条路,也就是通过BootstrapClassLoader引导类加载器加载,这也是为什么说BootstrapClassLoader是ExtClassLoader父加载器的原因。
然后是AppClassLoader的创建,再次引用Launcher的构造函数的部分代码:
var1 = Launcher.ExtClassLoader.getExtClassLoader(); //省略部分代码 this.loader = Launcher.AppClassLoader.getAppClassLoader(var1);//传入的就是上面的ExtClassLOader
后续调用链路为:
public static ClassLoader getAppClassLoader(final ClassLoader var0) throws IOException { final String var1 = System.getProperty("java.class.path"); final File[] var2 = var1 == null ? new File[0] : Launcher.getClassPath(var1); 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); } }); } AppClassLoader(URL[] var1, ClassLoader var2) { super(var1, var2, Launcher.factory); this.ucp.initLookupCache(this); }
getAppClassLoader()是通过AppClassLoader构造函数初始化的,我们需要关注的是其构造函数的第二个入参,也就是会被复制给parent变量的那个参数,从Launcher构造函数中可以看到,赋值给AppClassLoader的parent的是ExtClassLoader的实例,说明AppClassLoader的父加载器就是ExtClassLoader!
至此完成AppClassLoader、ExtClassLoader的创建。
3、findClass和loadClass的区别
//todo//如有错误,欢迎指正
参考:《揭秘Java虚拟机-JVM设计原理与实现》--封亚飞
相关文章推荐
- 类路径分析Java的类加载器与ClassLoader(二):classpath与查找类字节码的顺序,分析ExtClassLoader与AppClassLoader的源码
- 分析Java的类加载器与ClassLoader(二):classpath与查找类字节码的顺序,分析ExtClassLoader与AppClassLoader的源码
- 分析Java的类加载器与ClassLoader(二):classpath与查找类字节码的顺序,分析ExtClassLoader与AppClassLoader的源码
- Java的ClassLoader分析与Jettty的WebAppClassLoader
- 深入分析Java ClassLoader原理
- 深入分析Java ClassLoader原理
- 深入分析Java ClassLoader原理
- 分析AppClassLoader,ExtClassLoader 和URLClassLoader 的关系
- java 深入分析Java ClassLoader原理(实践操作)
- Jetty class loader 类加载过程源码分析初稿
- 深入分析Java ClassLoader原理
- 深入分析Java ClassLoader原理
- ClassLoader对Java源码加解密
- Android Loader(三) 结合CursorLoader分析Loader相关源码
- 分析AppClassLoader,ExtClassLoader 和URLClassLoader 的关系
- java的classLoader分析与jettty的WebAppClassLoader
- 深入分析Java ClassLoader原理
- java 反射机制中classloader的关系
- JAVA ClassLoader实例和Singleton模式结合
- ClassLoader源码分析