JVM的艺术—类加载器篇(二)

Stella981
• 阅读 573

分享是价值的传递,喜欢就点个赞

引言

今天我们继续来深入的剖析类加载器的内容。上节课我们讲了类加载器的基本内容,没看过的小伙伴请加关注。今天我们继续。

什么是定义类加载器和初始化类加载器?

  • 定义类加载器:假设我们的某一个类是由ExtClassLoader加载的,那么ExtClassLoader称为该类的定义类加载器

  • 初始化加载器:能够返回Class对象引用的都叫做该类的初始类加载器,比如类A是由我们的ExtClassLoader加载,那么

    ExtClassLoader是该类的定义类加载器,也是该类的初始类加载器,而我们的AppClassLoader也能返回我们A类的引用

    那么AppClassLoader也是该类的初始类加载器。

什么是类加载器的双亲委派模型?

上篇文章我们提到了类加载器的双亲委派模型,也可以称为双亲委托模型。今天这篇文章我们就来把这个概念给讲明白。

概念:用一种简单的方式去描述双亲委托的概念。可以分为两个部分去理解

1委托:

jvm加载类的时候是通过双亲委派的方式去加载,自下而上的去委托。

自定义类加载器需要加载类时,先委托应用类加载器去加载,然后应用类加载器又向扩展类加载器去委托,扩展类加载器在向启动类加载器去委托。

如果启动类加载器不能加载该类。那么就向下加载

2加载:

jvm加载类的时候是通过双亲委派的方式去加载委托,但是加载的时候是由上向下去加载的,当委托到最顶层启动类加载器的时候,无法在向上委托,那么

启动类加载器就开始尝试去加载这个类,启动类加载器加载不了就向下交给扩展类加载器去加载,扩展类加载器加载不了就继续向下委托交给应用类加载器

去加载,以此类推。

如果文字描述你还不清楚什么是双亲委托机制,那么我画了一幅图可以更清楚类加载的过程。如下:

JVM的艺术—类加载器篇(二)

通过上图,我们知道更能清楚的知道,双亲委托模型的工作机制,用一句简单的话说,就是需要加载一个类的时候,向上委托,向下加载。

注意:在双亲委派机制中,各个加载器按照父子关系形成树型结构,除了根加载器以外,每一个加载器有且只有一个父加载器。

接下来,我也从jdk底层源码的角度给大家画了一张类加载的主要过程,图如下:

JVM的艺术—类加载器篇(二)

以上就是类加载器加载一个类的重要过程步骤。希望各位小伙儿可以结合源码的方式,仔细再研究一下。其实还挺好理解的。

下面咱们再说说,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函数,看看结果。代码如下:

JVM的艺术—类加载器篇(二)

通过那行结果我们看出,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对应的类,反之不行

下面准备了一张图,以便于大家的理解。

JVM的艺术—类加载器篇(二)

上面这张图就很好的解释了命名空间的概念。大家可以再好好的体会一下。

我们光画图,光用嘴说并不是一种很有力的证据,就如同我写在这篇博文的时候所提,我们在学习和掌握某个概念的时候,就必须要拿出有力的证据,来证明自己的猜想或者是观点,那我们就举一个例子。来验证一下我们上面的理论是否正确。代码如下:

这是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

JVM的艺术—类加载器篇(二)

总结:通过上面的代码我们就可以看出来,我们在Person中去new一个Dog的实例的时候,并没有创建成功,而是抛出了Exception in thread "main" java.lang.NoClassDefFoundError: com/.........

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
待兔 待兔
4个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Wesley13 Wesley13
3年前
java动态加载类和静态加载类
一.什么是动态加载类什么是静态加载类Class.forName不仅表示类的类类型,还代表了动态加载类。编译时加载是静态加载类,运行时加载是动态加载类。请大家区分编译运行。二.为何要使用动态加载类我们写了一个程序并没有写A类和B类以及start方法 publicclassMain{publicstati
Wesley13 Wesley13
3年前
java类的加载与加载器
java代码在计算机中经历的三个阶段:1.Source源代码阶段(代码还是在硬盘上,并没有进入内存)  Student.java通过javac编译Student.class字节码文件2.类加载器ClassLoader将字节码文件加载进入内存,成为Class类对象(成员变量Field\\fields、构造方法Const
Wesley13 Wesley13
3年前
Java高级篇——深入浅出Java类加载机制
类加载器简单讲,类加载器ClassLoader的功能就是负责将class文件加载到jvm内存。类加载器分类从虚拟机层面讲分为两大类型的类加载器,一是BootstrapClassloader即启动类加载器(C实现),它是虚拟机的一部分,二是其他类型类加载器(JAVA实现),在虚拟机外部,并全部继
Wesley13 Wesley13
3年前
Java类加载机制
启动(Bootstrap)类加载器启动类加载器主要加载的是JVM自身需要的类,这个类加载使用C语言实现的,是虚拟机自身的一部分,它负责将<JAVA\_HOME/lib路径下的核心类库或Xbootclasspath参数指定的路径下的jar包加载到内存中,注意必由于虚拟机是按照文件名识别加载jar包的,如rt.jar,如果文件名不被虚拟机
Stella981 Stella981
3年前
JVM(四)JVM的双亲委派模型
1、两种不同的类加载器  从JAVA虚拟机的角度来讲,只存在两种不同的类加载器:一种是启动类加载器(BootstrapClassLoader),这个类加载器使用C语言实现,是虚拟机自身的一部分;另一种就是所有其他的类加载器,这些加载器都由Java语言实现,独立于虚拟机外部,并且全都继承自抽象类java,lang.ClassLoader。
Wesley13 Wesley13
3年前
Java虚拟机(五):JVM 类加载机制
一、JVM类加载机制JVM类加载机制分为五个部分:加载,验证,准备,解析,初始化,下面我们就分别来看一下这五个过程。!(https://oscimg.oschina.net/oscnet/0a45369b8a27e5e50b5f57d200ec45dcbdf.png)1\.加载:  加载是类加载过程中
Java类加载机制详解 | 京东云技术团队
一.类加载器及双亲委派机制|类加载器|加载类|备注||||||启动类加载器(BootstrapClassLoader)|JAVAHOME/jre/lib|无上级,无法直接访问由jvm加载||拓展类加载器(ExtensionClassLoader)|JAVA