Java ClassLoader
一 基本概念
Java中引入了虚拟机的概念,即在机器和编译程序之间加入了一层抽象虚拟的机器(JVM)。JVM屏蔽了各平台之间的差异,从而使Java具有了跨平台的特点。
Java程序并不是一个原生的可执行文件,而是由许多独立的.java文件组成。每一个文件对应一个Java类,编译之后会生成可以通过JVM执行的.class文件。类装载器(ClassLoader)的主要功能是将这些.class文件根据程序需要动态的加载到JVM中。
二 ClassLoader体系结构
类装载器在JVM中并不是唯一的,JVM自带了三个装载器,用户也可以根据自己的需求自定义新的装载器,这些装载器的体系结构可以看作是树状结构,如图1所示:
图1 类加载器树状组织结构示意图
JVM自带的三个类装载器分别是:
用来加载 Java的核心库,是用原生代码来实现的,并不继承自 java.lang.ClassLoader。主要负责jdk_home/lib目录下的核心 api 或 -Xbootclasspath 选项指定的jar包。
用来加载 Java的扩展库。Java 虚拟机的实现会提供一个扩展库目录。该类加载器在此目录里面查找并加载 Java 类。主要负责jdk_home/lib/ext目录下的jar包或 -Djava.ext.dirs 指定目录下的jar包装入工作。
功能描述:通常由AppClassLoader充当,它根据 Java应用的类路径(CLASSPATH)来加载 Java类。一般来说,Java 应用的类都是由它来完成加载的。可以通过 ClassLoader.getSystemClassLoader() 来获取它。主要负责java–classpath/-Djava.class.path所指的目录下的类与jar包装入工作。
在这些类装载器中,BootstrapClassLoader是比较特殊的一个。BootstrapClassLoader是JVM自带的类装载器,由C++编写,在JVM运行的时候就已经存在了。java的运行环境所需要的所有类库都由它来装载。ExtClassLoader和AppClassLoader本身也是java类,由BootstrapClassLoader装载到JVM之中。
下面用一个小例子测试三个JVM自带ClassLoader之间的关系:
例1 JVM自带ClassLoader关系的测试 (example 1)
public class ClassLoaderTest1 {
public static voidmain(String[] args) {
ClassLoaderloader = ClassLoaderTest1.class.getClassLoader();
System.out.println("ThisClass is loaded by :" + loader);
System.out.println("AppClassLoader'sfather classLoader is :" + loader.getParent());
System.out.println("ExtClassLoader'sfather classLoader is:" + loader.getParent().getParent());
}
}
运行结果
This Class is loaded by:sun.misc.Launcher$AppClassLoader@19821f
AppClassLoader'sfather classLoader is :sun.misc.Launcher$ExtClassLoader@addbf1
ExtClassLoader's father classLoader is:null
第一行表示,当前用户自定义的类由系统类装载器AppClassLoader进行装载。
第二行表示,系统类装载器的父加载器是扩展装载器ExtClassLoader。
第三行表示,扩展装载器的父加载器载器为启动类加载bootClassLoader(由bootStrap classloader 不是标准的java类,所以在这里我们只能看到null)。
三 ClassLoader装载策略
从1.2版本开始,Java引入了双亲委托模型,从而更好的保证了Java平台的安全。在此模型下,当一个装载器被请求装载某个类时,它首先委托自己的parent去装载,若parent能装载,则返回这个类所对应的Class对象,若parent不能装载,则由parent的请求者去装载。
具体顺序如图2所示:当Java中出现新的类,AppClassLoader首先将类传递给它的父类类装载器,也就是ExtClassLoader,询问它是否能够装载该类。如果能,那AppClassLoader就不再加载,同样ExtClassLoader在装载时,也会先问问它的父类装载器BootClassloader是否能加载,如果能则由BootClassLoader加载。当BootClassLoader无法加载的时候,则返回ExtClassloader一级,如果依然不能加载则再返回下面一级(AppClassLoader)。在基于类装载器的树状的结构图中,每个类装载器有自己的父亲,类装载器在装载类时,总是先让自己的父类装载器装载,如果父类装载器无法装载该类时,自己就会动手装载。如果自己也装载不了,则报Exception,class not found的错误。
图2 JVM ClassLoader加载策略
这种加载策略形成了以下java class加载的顺序:
1. 首先加载核心API,让系统最基本的运行起来
2. 加载扩展类
3. 加载用户自定义的类
在此模型下,系统会在用户自定义类加载之前,完成核心类的加载。也就是说,即使用户定义了一个与系统核心类一模一样的类,也无法覆盖父加载器已经加载的核心类,从而防止了由于用户重写系统类而带来的安全隐患。实际上,自定义类装载器的编写者可以自由定义装载规则,不把装载权交给父装载类,但是可能带来不安全的问题。
四 自定义ClassLoader及使用方法
有时候,用户需要根据自己的需要定制自己的ClassLoader。Java规范规定,所有的用户自定义ClassLoader都必须从抽象类“java.lang.ClassLoader”类继承而来。JVM在加载类的时候,是通过ClassLoader的loadClass方法来加载class的,在定制自己的ClassLoader之前,先看一下这个函数的内部实现。
protectedsynchronized Class loadClass(String name, boolean resolve)
throwsClassNotFoundException {
// 首先, 检查这个类是否已经加载。
Class c =findLoadedClass(name);
if (c == null){
try {
if(parent != null) {
c =parent.loadClass(name, false);
} else{
c =findBootstrapClass0(name);
}
} catch(ClassNotFoundException e) {
// 如果仍然没有找到,那么就调用findclass查找这个类.
c =findClass(name);
}
}
if (resolve) {
resolveClass(c);
}
return c;
}
通常我们使用ClassLoader.loadClass(String name)根据指定的类名得到一个相应的Class实例。从Java源代码中我们可以看到,缺省的ClassLoader做了如下的工作:
1. 调用FindLoadedClass(String):Class 检查一下这个Class是否已经被加载过了。由于JVM 规范规定ClassLoader可以在缓存保留它所加载的Class,因此如果一个Class已经被加载过,直接从缓存中获取即可。
2. 调用它的父类的LoadClass()方法,如果它的父类不为空,让它的父类加载器加载。
3. 如果它的父类加载器叠代后也无法加载这个Class,则使用JVM内部的ClassLoader(即著名的BootstrapClassloader)来加载这个Class。
如果上面三步都没有找到,就调用findClass方法从自己的加载空间查找加载这个Class。
因此,如果我们希望自定义的ClassLoader能够保持系统类装器的装载策略,并不需要重写loadClass方法,只要覆盖FindClass方法即可。
现在我们做一个最简单的自定义ClassLoader,这个ClassLoader继承了系统类装载器的装载策略,只是加载的路径不同。
例2 自定义ClassLoader使用测试(example 2)
/** */
/**
* 自定义类加载器
*/
public class MyClassLoader extends ClassLoader {
private staticString MyClasspath = new String("");
private staticHashtable loadClassHashTable = new Hashtable();
publicMyClassLoader() {
}
/** */
/**
* 构造自定义的加载器 参数1:路径名
*/
publicMyClassLoader(String MyClasspath) {
if(!MyClasspath.endsWith("\\")) {
MyClasspath= MyClasspath + "\\";
}
this.MyClasspath= MyClasspath;
}
/** */
/**
* 查找类并加载 参数1:文件名 被loadClass() 调用
*/
public ClassfindClass(String name) {
byte[]classData = null;
Class c = null;
try {
classData =loadClassData(name);
if(classData != null) {
c =defineClass(name, classData, 0, classData.length);
} else {
}
loadClassHashTable.put(name,c);
System.out.println("加载"+ name + "类成功。");
} catch(Exception e) {
e.printStackTrace();
}
return c;
}
/** */
/**
* 读取文件字节码 参数1:文件名 被findClass() 调用
*/
private byte[]loadClassData(String name) throws IOException {
String filePath= searchFile(MyClasspath, name + ".class");
System.out.println(filePath);
if (!(filePath== null || filePath == "")) {
FileInputStreaminFile = new FileInputStream(filePath);
byte[]classData = new byte[inFile.available()];
inFile.read(classData);
inFile.close();
returnclassData;
}
System.out.println("读取字节码失败。");
return null;
}
/** */
/**
* 查询文件 参数1:路径名 参数2:文件名
*/
public StringsearchFile(String classpath, String fileName) {
File f = newFile(classpath + fileName);
// 测试此路径名表示的文件是否是一个标准文件
if (f.isFile()){
returnf.getPath();
} else {
// 返回由此抽象路径名所表示的目录中的文件和目录的名称所组成字符串数组
Stringobjects[] = new File(classpath).list();
for (int i= 0; i < objects.length; i++) {
// 测试此抽象路径名表示的文件是否是一个目录
if (newFile(classpath + f.separator + objects[i])
.isDirectory()){
// 迭代遍历。separator是与系统有关的默认名称分隔符
returnsearchFile(classpath + f.separator + objects[i]
+f.separator, fileName);
}
}
}
System.out.println("没有找到文件:"+ fileName);
return null;
};
}
这个自定义MyClassLoader继承了父类ClassLoader默认的loadClass()方法,只是覆盖了findClass方法,寻找自己空间中的目标类。
下面用一个例子测试MyClassLoader的使用情况。(在D:/MyClass文件夹下拷贝一个名为HelloWorld.class的class文件用来测试)。
public class ClassLoaderTest2 {
public static voidmain(String[] args) throws Exception{
MyClassLoaderloader = new MyClassLoader("D:/MyClass");
Class clazz =loader.loadClass("HelloWorld");
clazz =loader.loadClass("HelloWorld");
Constructor con= clazz.getConstructor(new Class[]{});
//得到HelloWorld函数的构造函数
Object object =con.newInstance();
//用HelloWorld的构造函数创建一个新的实例
Method mtd =clazz.getDeclaredMethod("sayHello");
//得到HelloWorld中声明的"sayHello"方法
mtd.invoke(object,null);
//运行刚刚创建实例中的sayHello方法
System.out.println("Wholoads ClassLoaderTest ? "+ClassLoaderTest2.class.getClassLoader());
System.out.println("Wholoads HelloWorld ? "+clazz.getClassLoader());
System.out.println("Wholoads MyClassLoader ? "+MyClassLoader.class.getClassLoader());
}
}
运行结果
D:\MyClass\HelloWorld.class
加载HelloWorld类成功。
Hello World .
Who loads ClassLoaderTest ?sun.misc.Launcher$AppClassLoader@19821f
Who loads HelloWorld ? com.test.MyClassLoader@61de33
Who loads MyClassLoader ?sun.misc.Launcher$AppClassLoader@19821f
运行成功。从结果中可以看到,当前class由 AppClassLoader加载的。HelloWorld由指定的类加载器MyClassLoader加载。MyClassLoader的父加载器是AppClassLoader。
在这个例子中,为了调用了MyClassLoader中加载的HelloWorld.class的成员函数“sayHello”,采用了Java的反射机制。
Java反射机制是指在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意一个方法。更具体的原理可以参考链接中的文章:
http://bluedusk.iteye.com/blog/343408
五 类隔离性与名字空间
在Java中,一个类用其完全匹配类名(fully qualified class name)作为标识,这里指的完全匹配类名包括包名和类名。但在JVM中,一个类用其全名和一个加载类ClassLoader的实例作为唯一标识。因此,如果一个名为Pg的包中,有一个名为Cl的类,被类加载器KlassLoader的一个实例kl1加载,Cl的实例,即C1.class在JVM中表示为(Cl, Pg, kl1)。这意味着两个类加载器的实例(Cl, Pg, kl1) 和 (Cl, Pg, kl2)是不同的,被它们所加载的类也因此完全不同,互不兼容的。
图3 自定义ClassLoader结构示意图
在如图3所示的结构中,ClassLoaderA、ClassLoaderB都是由用户自定义的ClassLoader,我们可以发现,A与B的之间不存在父子关系,处于不同的分支之下。根据上面对双亲委托模型和隔离性的分析,分别使用它们装载同一个类,也会在内存中出现两个Class类的实例。
例3 类隔离机制测试1 (example 3)
public class ClassLoaderTest3 {
public static voidmain(String[] args) throws Exception {
MyClassLoaderclassLoaderA = new MyClassLoader("D:/MyClass");
MyClassLoaderclassLoaderB = new MyClassLoader("D:/MyClass");
Class class1 =classLoaderA.loadClass("HelloWorld");
Class class2 =classLoaderA.loadClass("HelloWorld");
Class class3 =classLoaderB.loadClass("HelloWorld");
System.out.println("obj1'sclassloader is : " + class1.getClassLoader());
System.out.println("obj2'sclassloader is : " + class2.getClassLoader());
System.out.println("obj3'sclassloader is : " + class3.getClassLoader());
if(class1 ==class2){
System.out.println("class1and class2 are same class.");
}
else{
System.out.println("class1and class2 are different.");
}
if(class1 ==class3){
System.out.println("class1and class3 are same class.");
}
else{
System.out.println("class1and class3 are different.");
}
}
}
运行结果
D:\MyClass\HelloWorld.class
MyClassLoader 加载HelloWorld类成功。
D:\MyClass\HelloWorld.class
MyClassLoader 加载HelloWorld类成功。
obj1's classloader is : MyClassLoader@61de33
obj2's classloader is : MyClassLoader@61de33
obj3's classloader is : MyClassLoader@ca0b6
class1 and class2 are same class.
class1 and class3 are different.
从例3中可以看到,虽然三个Class的ClassLoader都是同一类型的,并且都是存放在同一目录下的同一个文件中,但是在同一ClassLoader下的类在JVM中被看作是相同的Class,而在不同的ClassLoader下加载的Class却被视为是完全不同的类。可以这样理解,class1在JVM中的名字是(com.test.classloader.MyClassLoader@61de33)HelloWorld.class而Class3在JVM中的名字是(com.test.classloader.MyClassLoader@ca0b6)HelloWorld.class。所以JVM认为两者是不同的。
同样,我们无法通过普通的类调用方法去使用另一个ClassLoader中的.class文件。
例4 类隔离机制测试2 (example 4)
首先定义一个类A.java,提供getTime方法返回系统当前时间,编译为.class文件后放在“c:/A”文件夹下:
public class A {
public String getTime(){
Date data = new Date();
returndata.toString();
}
}
定义另一个类B,B类依赖于A类,编译为.class文件后放在“c:/B”文件夹下:
public class B {
A dp;
public voidsayHello(){
dp = new A();
System.out.println("HelloWorld, the system time is " +"'"+ dp.getTime() + "'" +".");
}
}
编写ClassLoaderTest类,用来测试自定义ClassLoader的运行情况。
public class ClassLoaderTest4 {
public static voidmain(String[] args) throws Exception {
// TODOAuto-generated method stub
MyClassLoaderloaderA = new MyClassLoader("c:/A/");
Class clazzA =loaderA.loadClass("A");
MyClassLoaderloaderB = new MyClassLoader("c:/B/");
Class clazzB =loaderB.loadClass("B");
Constructor con= clazzB.getConstructor(new Class[]{});
Object object =con.newInstance();
Method mtd =clazzB.getDeclaredMethod("sayHello");
mtd.invoke(object,null);
}
}
运行结果
c:\A\A.class
MyClassLoader 加载A类成功。
c:\B\B.class
MyClassLoader 加载B类成功。
没有找到文件:A.class
null
读取字节码失败。
java.lang.NullPointerException
atjava.util.Hashtable.put(Unknown Source)
atMyClassLoader.findClass(MyClassLoader.java:51)
atjava.lang.ClassLoader.loadClass(Unknown Source)
atjava.lang.ClassLoader.loadClass(Unknown Source)
atB.sayHello(B.java:6)
atsun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
atsun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
atsun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
atjava.lang.reflect.Method.invoke(Unknown Source)
atClassLoaderTest.main(ClassLoaderTest.java:23)
Exception in thread "main"java.lang.reflect.InvocationTargetException
atsun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
atsun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
atsun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
atjava.lang.reflect.Method.invoke(Unknown Source)
atClassLoaderTest.main(ClassLoaderTest.java:23)
Caused by: java.lang.NoClassDefFoundError: A
atB.sayHello(B.java:6)
... 5 more
从Java ClassLoader运行原理分析一下例4产生错误的原因。
B.class是由自定义加载器classLoaderB加载运行的,当执行到“A dp;”的时候,classLoaderB无法通过自己的父类加载器找到A.class,也无法在自己的加载路径中找到这个类,所以报出了以上的错误。
OSGi ClassLoader
一 OSGi ClassLoader体系结构
在Java中ClassLoader是非常重要的概念,而大家也知道,JVM本身在ClassLoader上并没有提供非常强大的功能,比如模块开发非常重要的模块隔离ClassLoader的机制、版本加载机制等。OSGI基于JVM ClassLoader形成模块隔离ClassLoader的机制,同时也增强了ClassLoader按版本加载、属性过滤等多种功能。
图4 BundleClassloader结构示意图
OSGi ClassLoader体系结构如图4所示:对于每一个Bundle,都有一个独立的Bundle ClassLoader用来加载其Classpath中指定的jar以及指定路径下的文件。每个独立的ClassLoader可以通过下面几个区域来加载类和资源:
l Boot Classpath :包含java.*以及其实现的包。
l FrameworkClass path :框架通常会为其实现的类建立一个单独的ClassLoader。
l Bundle Space :Bundle Space则包含了与这个Bundle有关的所有的jar文件。
其中 Bundle Space是指通过一个给定的Bundle的Classloader可以找到的类,对于一个Bundle而言,它的ClassLoader能找到的类包括:
l ParentClassloader加载的类;
l 当前BundleImported的packages;
l 当前BundleRequired的Bundles的类;
l Bundle自己的Classpath中的类;
我们用“找到”这个词,而不是“加载”,因为每个BundleClassloader只能加载当前Bundle Classloader定义路径下的class文件。对于其他区域的类,它是通过由FrameworkClassloader 定义的交互关系找到这个class所在的区域,并同过该区域的ClassLoader进行加载。
具体加载流程如图5所示:
图5 Bundle Classloader加载流程图
如图5,OSGI框架在加载Bundle中的类时,按照这样的步骤进行:
l 如需要加载的为java.*的类,则直接委派给Parent Classloader,如在parent Classloader中找到了相应的类,则直接返回,如未找到,则抛出NoClassDefFoundException。
l 如加载的不是java.*的类,则进入这一步。判断加载的类是否属于boot delegation中配置的范围,如不属于则进入下一步,如属于则继续委派给Parent Classloader,如在Parent Classloader中找到则直接返回,如未找到,则进入下一步。可在配置文件中编写org.osgi.framework.bootdelegation的属性来决定boot delegation的范围,示例:
org.osgi.framework.bootdelegation=sun.*,com.sun.*
l 如属于Bundle Import package中的类,则交给export package的Bundle的classloader进行加载,如加载失败,则直接抛出NoClassDefFoundException,如加载成功则直接返回。这步就解释了之前在注意事项中所写的需要注意的包的问题。
l 如不属于Bundle Import package中的类,则搜索是否属于Require Bundles中export的package的类,如属于则交由export package的Bundle的Classloader进行加载,如加载成功则直接返回,如加载失败则进入下一步。
l 在Bundle classpath(就是在Bundle-Classpath所配置的路径)中搜索需要加载的类,如加载成功,则直接返回,如加载失败则继续进入下一步。
l 搜索Fragment Bundle的classpath,如载成功,则直接返回,如加载失败则继续进入下一步。
l 判断是否属于export的package,如属于则直接抛出NoClassDefFoundException,如不属于则进入下一步。
l 判断是否属于DynamicImport的package,如不属于则直接抛出NoClassDefFoundException,如属于则使用export package的Bundle的ClassLoader进行加载,如加载成功则直接返回,如加载失败则抛出NoClassDefFoundException。
二 OSGi ClassLoader实现原理
根据前面对Java ClassLoader的讲解可以看到,不同ClassLoader之间形成了两个不同的名字空间,那么OSGi是如何实现让两个不同的ClassLoader中的Class互相交互的呢。秘密就在于Bundle ClassLoader共同的父加载器:Framework ClassLoader。它重新定义了从它以下的ClassLoader的加载顺序,使各个Bundle ClassLoader中加载的内容可以通过某种规则交互。
例5 OSGi ClassLoader 实现原理测试 (example 5)
首先定义一个类A.java,提供getTime()方法返回系统当前时间,编译为.class文件后放在“c:/A” 文件夹下:
public class A {
public String getTime(){
Date data = new Date();
returndata.toString();
}
}
定义另一个类B,B类依赖于A类,编译为.class文件后放在“c:/B”文件夹下:
public class B {
A dp;
public voidsayHello(){
dp = new A();
System.out.println("(A)dpin B use this classloader : " + dp.getClass().getClassLoader());
System.out.println("A'sfather classloader is : " + dp.getClass().getClassLoader().getParent());
System.out.println("Buse this classloader : " + this.getClass().getClassLoader());
System.out.println("B'sfather classloader is : " + this.getClass().getClassLoader().getParent());
System.out.println("HelloWorld, the system time is " +"'"+ dp.getTime() + "'" +".");
}
}
为了模拟OSGi中不同Bundle ClassLoader中的情况,初始化两个ClassLoader,一个只能看见A,一个只能看见B。就好像A,B是两个有依赖的osgi plug-in一样。自定义ClassLoader类如下:
public class MyClassLoader extends URLClassLoader {
public URL url;
publicMyClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
url = urls[0];
}
protectedClass> findClass(String name) throws ClassNotFoundException {
Class ret =findLoadedClass(name);
if (ret ==null) {
ret =super.findClass(name);
}
return ret;
}
}
自定义的ClassLoader继承了Java ClassLoader的加载方式,首先让自己的父加载类尝试加载目标文件。我们实现一个类似于osgi环境那个掌管所有插件之间复杂依赖魔力父亲ClassLoader,让它作为之前两个ClassLoader的父亲。当通过正常的加载策略无法找到文件时,此MyMgrClassLoader会去A的ClassLoader中寻找。
public class MyMgrClassLoader extends ClassLoader {
privateMyClassLoader a;
privateMyClassLoader b;
public MyMgrClassLoader(ClassLoaderparent) {
super(parent);
}
protectedsynchronized Class> loadClass(String name, boolean resolve)
throwsClassNotFoundException {
Class ret =null;
try {
ret =super.loadClass(name, resolve);
//首先按照JavaClassLoader的规则寻找目标文件
} catch(Exception e) {
ret =a.findClass(name);
//如果找不到,通过a的Classloader寻找目标文件
System.out.println("BClassLoader and I(MyMgrClassLoader) can't find class '"+name+ "', itis loaded by A ClassLoader.");
}
return ret;
}
publicMyClassLoader getA() {
return a;
}
public voidsetA(MyClassLoader a) {
this.a = a;
}
publicMyClassLoader getB() {
return b;
}
public voidsetB(MyClassLoader b) {
this.b = b;
}
}
测试一下:
public class ClassLoaderTest5 {
public static voidmain(String[] args) throws Exception {
MyMgrClassLoadermgr = new MyMgrClassLoader(ClassLoader.getSystemClassLoader());
//建立ClassLoader管理者“mgr”
MyClassLoadermy1 = new MyClassLoader(new URL[] { new URL("file:///c:/A/") }, mgr);
MyClassLoadermy2 = new MyClassLoader(new URL[] { new URL("file:///c:/B/") }, mgr);
//建立A的ClassLoader“my1”和B的ClassLoader“my2”它们将mgr作为自己的父加载器,接受父加载器的管理
mgr.setA(my1);
mgr.setB(my2);
//将my1和my2传入mgr中
Class c2 =my2.loadClass("B");
Object object =c2.newInstance();
Method mtd =c2.getDeclaredMethod("sayHello");
mtd.invoke(object);
System.out.println("SUCCESS");
//运行B.class的sayHello方法
}
}
运行结果
B ClassLoader and I(MyMgrClassLoader) can't find class'A', it is loaded by A ClassLoader.
(A)dp in B use this classloader : MyClassLoader@ca0b6
A's father classloader is : MyMgrClassLoader@14318bb
B use this classloader : MyClassLoader@61de33
B's father classloader is : MyMgrClassLoader@14318bb
Hello World, the system time is 'Tue May 03 15:00:19 CST2011'.
SUCCESS
运行成功,在B中成功实例化了A,A、B分别由各自的ClassLoader加载,由它们共同的父加载器MyMgrClassLoader管理它们的交互关系。当B的ClassLoader尝试加载一个文件的时候的时候,它首先请求自己的父加载器MyMgrClassLoader加载该类,当MyMgrClassLoader无法通过自己的父类及自身加载这个class文件的时候,它会让A的ClassLoader去寻找该文件,从而成功加载A和B。