深入理解JVM 类加载器、双亲委派

按理解描述,欢迎指正,欢迎交流。

先上图:


四种类加载器:根加载器、扩展加载器、系统加载器、自定义加载器。其中根加载器加载其他加载器,根加载器是由从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加载了两次,原因是,两个类加载器命名空间互相隔离,所以只能自己加载自己的。

你可能感兴趣的:(深入理解JVM 类加载器、双亲委派)