按理解描述,欢迎指正,欢迎交流。
先上图:
四种类加载器:根加载器、扩展加载器、系统加载器、自定义加载器。其中根加载器加载其他加载器,根加载器是由从c++实现的,随虚拟机的启动而启动。
类加载:将类的.class文件的二进制数据读到内存中,将其放在运行时数据区的方法区中,然后在内存中生成java.lang.Class对象,用于封装类在方法区中的数据结构。
Binarynames:"java.lang.String","javax.swing.JSpinner$DefaultEditor","java.security.KeyStore$Builder$FileBuilder$1", "java.net.URLClassLoader$3$1"。这是jdk给的例子,带$的是内部类,$+数字的是匿名内部类。
类对象:如何确定一个类对象的唯一性呢,是类的Binarynames + 加载它的类加载器
双亲委派:如图上所示,java是动态加载所需的类的,当一个类需要加载时,从下往上检查是否已经被上面的加载器加载过,如果有则返回对象,如果没有则从上往下加载。每个加载器都有自己查找的范围。下面的加载器持有上面加载器的引用,比如AppClassLoader 持有 ExtClassLoader的引用 。
命名空间:类加载器及其之上类加载器所加载的类构成的空间。
引用类的加载:A引用B,默认用加载A的加载器加载B,加载B的过程遵循双亲委派。
双亲委派打破:
1、spi调用。需要上层类加载器加载的类,调用下层的类。
2、tomcat等容器存在对,类隔离、热部署等需求,对双亲委派进行打破。
类的卸载:自定义类加载器加载的类,可以被卸载。
talk is cheap,show you code:
示例1:获取类加载器的方式。
这段代码输出为:sun.misc.Launcher$AppClassLoader@18b4aac2、sun.misc.Launcher$AppClassLoader@18b4aac2、sun.misc.Launcher$AppClassLoader@18b4aac2、sun.misc.Launcher$AppClassLoader@18b4aac2
示例2:三种 类加载器
输出结果:sun.misc.Launcher$AppClassLoader@18b4aac2、sun.misc.Launcher$ExtClassLoader@3f99bd52、null、null
我们看到根加载器的显示为null、String的加载器为根加载器。
示例3:三种类加载器的查找路径
示例4:根据jdk的doc例子,实现自定义加载器
继承ClassLoader,重写findClass方法。定义了加载路径,并将类文件复制到path下。
测试一下自定义的类加载器:
这段代码分别用我们自定义的类加载器,加载了一下testDemo1 和 String类,我们发现加载这两个类,最终均不是用我们的类加载器加载的,分别是系统加载类和根加载类。为什么是这样呢?
这肯定跟我们的自定义类加载器的实现有关,我们看一下默认的构造器是调用super();我们进具体的实现看一下代码。
我们发现,实际上我们的自定义类加载器的持有的parent为getSystemClassLoader获得的。
最终我们发现,原来我们自定义的加载器的parent是appClassLoader。
过程是这样的,我们用myClassLoader1加载TestDemo1时,根据双亲委派机制,从底向上先查找是不是已经加载过TestDemo1,发现没有加载过。然后BootStrap先尝试加载失败,然后Ext尝试加载失败,然后App尝试加载成功。所以并不会用到我们的自定义加载器。
String类是在查找阶段就找到了,已经被boot加载过了,所以返回加载过的对象。
那怎样能使我们的自定义加载器起作用呢?有两个方向可以尝试一下,一个是删掉classpath下的TestDemo1.class文件,另一个是将自定义加载器的parent指定为Ext。我们分别尝试一下。
删掉class path下的TestDemo1.class文件结果如下:
我们看到,TestDemo1由我们的自定义加载器加载。原因是,app在尝试加载的时候,没有加载成功,所以自定义加载器负责加载。
我们尝试一下将我们的自定义加载器的parent设置为Ext
我们发现,一样得到了我们想要的效果。原因是,直接跳过了appClassLoader。当ExtClassLoaderj加载失败之后,直接由自定义加载器加载。
示例5:唯一确定一个类对象,Binarynames+类加载器
这段代码都使用的app加载,所以他们是同一个对象。我们将classpath下的class文件删除:
我们发现,类加载器是同一个类的不同实例,是不同的类加载器。虽然加载的是同一个份class文件,但是是不同的类对象。
示例6:双亲委派机制及引用类的加载。
我们发现,这两个都是app加载的。我们将TestDemo2.class从classpath删掉。看一下结果。
结果,demo2由自定义加载,而demo1由app加载
我们把TestDemo1.class也从classpath删掉
两个类都是由自定义加载器加载,我们只删除TestDemo1.class看一下结果
发现,加载demo1时报错。为什么会这样呢。规则时这样的,demo2引用demo1,demo1默认使用demo2的类加载器。当classpath下删除demo1,存在demo2时,demo2的类加载器为appClassLoader,demo1的类加载器也是appClassLoader。但是app在classpath下找不到demo1的class文件。
那为什么删除demo2没事儿呢,删除demo2,demo2的类加载器是自定义加载器,demo1使用自定义加载器加载时,遵循双亲委派,由appClassLoader加载
两个都删除的时候,两个都由自定义加载器加载。
示例7:命名空间,下层能引用上层,上层不能引用下层。
demo1引用demo2,myClassLoader2的parent为myClassLoader1。
直接运行,结果:
如果将demo1的class从classpath下删除:
可以看到,demo1由自定义加载器加载,demo2由app加载,程序正常运行
那如果删掉demo2、保留demo1呢?
发现demo1由app加载,而demo2由自定义加载器加载,程序报错的原因时demo1内引用了demo2,但是demo1的命名空间处于demo2的上层,所以demo1引用不到demo2,所以报错。
如果代码改成这样,两个自定义加载器平级,并将demo1和demo2从class path删除:
运行结果:
发现demo2加载了两次,原因是,两个类加载器命名空间互相隔离,所以只能自己加载自己的。