分享是价值的传递,喜欢就点个赞
引言
今天我们继续来深入的剖析类加载器的内容。上节课我们讲了类加载器的基本内容,没看过的小伙伴请加关注。今天我们继续。
什么是定义类加载器和初始化类加载器?
定义类加载器:假设我们的某一个类是由ExtClassLoader加载的,那么ExtClassLoader称为该类的定义类加载器
初始化加载器:能够返回Class对象引用的都叫做该类的初始类加载器,比如类A是由我们的ExtClassLoader加载,那么
ExtClassLoader是该类的定义类加载器,也是该类的初始类加载器,而我们的AppClassLoader也能返回我们A类的引用
那么AppClassLoader也是该类的初始类加载器。
什么是类加载器的双亲委派模型?
上篇文章我们提到了类加载器的双亲委派模型,也可以称为双亲委托模型。今天这篇文章我们就来把这个概念给讲明白。
概念:用一种简单的方式去描述双亲委托的概念。可以分为两个部分去理解
1委托:
jvm加载类的时候是通过双亲委派的方式去加载,自下而上的去委托。
自定义类加载器需要加载类时,先委托应用类加载器去加载,然后应用类加载器又向扩展类加载器去委托,扩展类加载器在向启动类加载器去委托。
如果启动类加载器不能加载该类。那么就向下加载
2加载:
jvm加载类的时候是通过双亲委派的方式去加载委托,但是加载的时候是由上向下去加载的,当委托到最顶层启动类加载器的时候,无法在向上委托,那么
启动类加载器就开始尝试去加载这个类,启动类加载器加载不了就向下交给扩展类加载器去加载,扩展类加载器加载不了就继续向下委托交给应用类加载器
去加载,以此类推。
如果文字描述你还不清楚什么是双亲委托机制,那么我画了一幅图可以更清楚类加载的过程。如下:
通过上图,我们知道更能清楚的知道,双亲委托模型的工作机制,用一句简单的话说,就是需要加载一个类的时候,向上委托,向下加载。
注意:在双亲委派机制中,各个加载器按照父子关系形成树型结构,除了根加载器以外,每一个加载器有且只有一个父加载器。
接下来,我也从jdk底层源码的角度给大家画了一张类加载的主要过程,图如下:
以上就是类加载器加载一个类的重要过程步骤。希望各位小伙儿可以结合源码的方式,仔细再研究一下。其实还挺好理解的。
下面咱们再说说,java采用双亲委托的方式去加载类,这样做的好处是什么呢?
双亲委派模型的好处
总所周知:java.lang.object类是所有类的父类,所以我们程序在运行期间会把java.lang.object类加载到内存中,假如java.lang.object类
能够被我们自定义类加载器去加载的话,那么jvm中就会存在多份Object的Class对象,而且这些Class对象是不兼容的。
所以双亲委派模型可以保证java核心类库下的类型的安全。
借助双亲委派模型,我们java核心类库的类必须是由我们的启动类加载器加载的,这样可以确保我们核心类库只会在jvm中存在一份
这就不会给自定义类加载器去加载我们核心类库的类。
根据我们的演示案例,一个class可以由多个类加载器去加载,同时可以在jvm内存中存在多个不同版本的Class对象,这些对象是不兼容的。
并且是不能相互转换的。
什么是全盘委托加载?
解释:假如我们的Person类是由我们的系统类APP类加载器加载的,而person类所依赖的Dog类也会委托给App系统类进 行加载,这个委托过程也遵循双亲委派模型。代码如下
- person类代码中创建Dog实例
public class Person {
public Person(){ new Dog(); }
}
public class Dog { public Dog(){ System.out.println("Dog 的构造函数"); }}
测试类
public class MainClass02 { public static void main(String[] args) throws Exception { //创建自定义类加载器的一个实例,并且通过构造器指定名称 Test01ClassLoader myClassLoader = new Test01ClassLoader("loader1"); myClassLoader.setPath("I:\\test\\"); Class<?> classz = myClassLoader.loadClass("com.test.Person"); System.out.println(classz.getClassLoader()); System.out.println(Dog.class.getClassLoader()); }}运行结果:sun.misc.Launcher$AppClassLoader@18b4aac2sun.misc.Launcher$AppClassLoader@18b4aac2Process finished with exit code 0
从上面的运行结果,我们可以看出,当我们用自定义类加载器去加载我们的Person的时候,根据双亲委托模型,我们的Person并没有被自定义类加载(Test01ClassLoader)加载,而是被AppClassloader加载成功,同时根据全盘委托规则,我们的Dog类也被AppClassLoader加载了。所以大家一定要记住这个至关重要的结论。为我们后面的学习打下坚实的基础。
下面我们在看一个例子。我们把类路径下的Person.class文件删除掉,然后再运行一下上面的main函数,看看结果。代码如下:
通过那行结果我们看出,Person类是由我们的自定义类加载器加载的。那为什么Dog类没有进行全盘委托的,这是因为双亲委托模型的缘故,我们的类路径下并没有Person类,故此AppClassLoader是无法加载我们的路径I:\\test\\下的com.test.Person.class文件的。所以Person类是由我们自定的类加载器加载的。再看Dog类,由于它的加载要遵循双亲委托模型,因为类路径下有Dog.class文件,所以AppClassLoader就可以加载Dog类。故此加载Dog类的ClassLoader是AppClassLoader。写到这里,大家对类加载已经有了一个非常深刻的理解。那么java为什么使用双亲委托模型的好处我相信已经不言而喻了。那么下面来说说双亲委托模型,有没有他的弊端呢,或者说有什么不好的地方嘛?我们可以打破这种双亲委托的方式去加载类嘛?下面我们来看一个例子。
类加载器的命名空间
说到双亲委托模型的弊端,那我就离不开命名空间的概念。
类加载器的命名空间 是由类加载器本身以及所有父加载器所加载出来的binary name(full class name)组成.
①:在同一个命名空间里,不允许出现二个完全一样的binary name。
②:在不同的命名空间种,可以出现二个相同的binary name。当时二者对应的Class对象是相互不能感知到的,也就是说Class对象的类型是不一样的。
解释:同一个Person.class文件 被我们的不同的类加载器去加载,那么我们的jvm内存中会生成二个对应的Person的Class对象,而且这二个对应的Class对象是相互不可见的(通过Class对象反射创建的实例对象相互是不能够兼容的不能相互转型**
③:子加载器的命名空间中的binary name对应的类中可以访问 父加载器命名空间中binary name对应的类,反之不行
下面准备了一张图,以便于大家的理解。
上面这张图就很好的解释了命名空间的概念。大家可以再好好的体会一下。
我们光画图,光用嘴说并不是一种很有力的证据,就如同我写在这篇博文的时候所提,我们在学习和掌握某个概念的时候,就必须要拿出有力的证据,来证明自己的猜想或者是观点,那我们就举一个例子。来验证一下我们上面的理论是否正确。代码如下:
这是Person类的代码。
package com.test;public class Person { public Person() { new Dog(); System.out.println("Dog的classLoader:-->"+ Dog.class.getClassLoader()); } static{ System.out.println("person类被初始化了"); }}
这是Dog类的代码。
package com.test;public class Dog { public Dog(){ System.out.println("Dog 的构造函数"); }}
具体的验证思路是这样的,首先我们把Person类的Class文件放到启动类加载器的加载目录下(C:\Program Files\Java\jdk1.8.0_144\jre\classes 这是启动类加载器的加载目录)来达到Person类交给启动类加载器加载的目的。
然后呢,我们让Dog类去被AppClassLoader(系统类加载器去加载)。然后我们在Person类中去访问Dog类。看看能否访问成功。
测试环境:把我们的Person.class放置在C:\Program Files\Java\jdk1.8.0_131\jre\classes这个目录下,那么我们的Person.class就会被我们的启动类加载器加载,而我们的Dog类是被AppClassLoader进行加载,我们的Person类 中引用我们的Dog类会抛出异常.
创建main方法进行测试:
package com.test;import java.lang.reflect.Method;/** * jvm 类加载器 第一章 * @author 奇客时间-时光 * 自定义类加载器——命名空间 * 测试父加载所加载的类,不能访问子加载器所加载的类。 */public class MainClass02 { public static void main(String[] args) throws Exception { System.out.println("Person的类加载器:"+Person.class.getClassLoader()); System.out.println("Dog的类加载器:"+Dog.class.getClassLoader()); Class<?> clazz = Person.class; clazz.newInstance(); }}运行结果: "C:\Program Files\Java\jdk1.8.0_144\bin\java.exe" "-javaagent:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\lib\idea_rt.jar=59226:C:\Program Files\JetBrains\IntelliJ IDEA 2019.2\bin" -Dfile.encoding=UTF-8 -classpath "C:\Program Files\Java\jdk1.8.0_144\jre\lib\charsets.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\deploy.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\access-bridge-64.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\cldrdata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\dnsns.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jaccess.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\jfxrt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\localedata.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\nashorn.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunec.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunjce_provider.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunmscapi.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\sunpkcs11.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\ext\zipfs.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\javaws.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jce.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfr.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jfxswt.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\jsse.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\management-agent.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\plugin.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\resources.jar;C:\Program Files\Java\jdk1.8.0_144\jre\lib\rt.jar;I:\jvm\out\production\jvm-classloader" com.test.MainClass02Person的类加载器:nullDog的类加载器:sun.misc.Launcher$AppClassLoader@18b4aac2person类被初始化了Exception in thread "main" java.lang.NoClassDefFoundError: com/test/Dog at com.test.Person.<init>(Person.java:7) at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) at java.lang.reflect.Constructor.newInstance(Constructor.java:423) at java.lang.Class.newInstance(Class.java:442) at com.test.MainClass02.main(MainClass02.java:20)Process finished with exit code 1
总结:通过上面的代码我们就可以看出来,我们在Person中去new一个Dog的实例的时候,并没有创建成功,而是抛出了Exception in thread "main" java.lang.NoClassDefFoundError: com/.........