您的位置:首页 > 编程语言 > Java开发

结合源码分析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设计原理与实现》--封亚飞
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: