JVM(七):类加载器模型

从虚拟机的角度出发,只存在两种类加载器,一种是启动类加载器(BootStrap ClassLoader),对于HotSpot来说,启动类加载器是由C++语言来实现的,但可能对于其他厂家的虚拟机,比如MRP、Maxint等,整个虚拟机都是纯Java语言编写的;而另外一种则是其他类加载器,这些类加载器都是使用Java代码来进行实现的,独立于Java虚拟机,并且全都继承自抽象类java.lang.ClassLoader

但是从开发人员的角度来看的话,就应该划分得更加细致一些,从架构上来看,Java将类加载器划分成了两类

  • 三层类加载器

  • 双亲委派的类加载器

下面就看看这两种的类加载器是怎样的

常用的类加载器

首先我们要先认识一下,虚拟机常用的类加载器

绝大多数的Java程序都会使用到以下3个系统提供的类加载来进行加载

  • 启动类加载器

  • 扩展类加载器

  • 应用程序类加载器

启动类加载器

启动类加载器是负责加载存放在java_home下的lib目录或者-Xbootclasspath参数所指定的路径所存放的jar,并且是Java虚拟机能够识别的类库,启动类加载器会将这些类库加载到虚拟机的内存中等待使用

启动类加载器是无法被应用程序直接使用的,如果我们在自定义类加载器的时候,如果需要把加载请求委派给引导类加载器去处理,最好的做法其实就是让自定义类加载的时候为null,就是不去定义类加载器。。。。。。感觉理解起来很绕口

直接看代码吧,下面是Class实例去获取自身类加载器的方法

在这里插入图片描述

从代码上看不出什么东西,所以我们从注释上去理解

在这里插入图片描述

注释上说明,该方法返回该class所使用的ClassLoader,一些类加载器实现类可能会使用null来表示bootstrap classLoader(启动类加载器),所以返回了getClassLoader方法返回为null,加旧代表使用的是启动类加载器

扩展类加载器

扩展类加载器是使用Java代码来实现的,并且是Launcher下面的一个内部类

在这里插入图片描述

ExtClassLoader负责加载lib目录下的ext路径的,并且从名字可以看出,这个是对于Java类库的一种扩展机制,因为该ExtClassLoader是使用Java来实现的,所以我们可以直接在应用程序上使用ExtClassLoader来加载Class文件

应用类加载器

这个应用类加载器与扩展类加载器一样,同样是使用Java语言来实现,同样是Launch类下面的内部类,同样也可以直接使用,名称为AppClassLoader,同时AppClassLoader被称为系统类加载器,这是因为ClassLoader.getSystemClassLoader的返回值就是它

在这里插入图片描述

应用类加载器负责加载用户类路径上所有的类库

这里先说明这个用户类路径,经过输出打印的结果如下

在这里插入图片描述

D:\Program Files\Java\jdk1.8.0_152\jre\lib\charsets.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\deploy.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\access-bridge-64.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\cldrdata.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\dnsns.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\jaccess.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\jfxrt.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\localedata.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\nashorn.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunec.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunjce_provider.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunmscapi.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\sunpkcs11.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\ext\zipfs.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\javaws.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\jce.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\jfr.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\jfxswt.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\jsse.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\management-agent.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\plugin.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\resources.jar

D:\Program Files\Java\jdk1.8.0_152\jre\lib\rt.jar

D:\代码\leetcode\out\production\leetcode

可以看到类路径代表着lib、还有lib\ext下的,最重要的还有out文件夹下的,要知道out文件夹里面就是java项目里面编译出来的所有Class文件,所以应用类加载器主要就是负责加载我们自己写出来的Java代码编译出来的Class文件

双亲委派模型

下面我们直接看这个双亲委派模型是怎样的一个模型

在这里插入图片描述

可以看到,这个双亲委派模型跟继承关系十分类似,但其实两者并不是同种概念,要进行区分开来

在这里插入图片描述

我们可以看到,AppClassLoader并没有继承ExtClassLoader

但我们通过AppClassLoader的getParent方法去获取上层加载器并打印出来的时候,可以知道AppClassLoader的上层加载器就是ExtClassLoader

在这里插入图片描述

那么Java到底是如何实现这个双亲委派模型的呢???

双亲委派模型要求除了顶层的启动类服务器外,其余的类加载器都应有自己的父类加载器,不过这里的父类加载器并不是以继承的关系来实现的,而是使用组合的形式来实现的,从而复用父类的代码(这也是为什么在Launcher下没有看到AppClassLoader继承了ExtClassLoader),组合的形式也很简单,通过父加载器是子加载器的成员属性,这样就是组合了

但双亲委派模型只是推荐的一种形式,也就是说通常是这种模型,但其实双亲委派模型并不是一个具有强制约束力的模型,仅仅只是Java设计者们推荐给开发者的一种类加载器实现的最佳实践

双亲委派模型的工作过程

都说是双亲委派的,肯定要先考虑双亲呀

双亲委派模型的工作过程是:如果一个类加载器收到了类加载的请求,它首先不会去加载这个类,而是把加载类的请求委派到父类加载器去完成,每一个层次的类加载器也是如此,如果父类加载器也有自己的父类加载器,那么也不会自己去加载,而是把加载类的请求委派到其父类加载器去完成,依次类推,导致的结果就是所有的加载请求的最终都会被传送到最顶层的启动类加载器中,只有当父类的加载器反馈自己无法完成这个加载请求时,子类加载器才会尝试自己去完成

那使用双亲委派模型有什么好处呢???

第一个显而易见的好处就是Java中的类随着它的类加载器一起具备了一种带有优先级的层次关系,正是这种优先级的层次关系保证了类加载出来都是一样的,前面提到过,类和其类加载器才能确定该类的唯一性,假如使用不同的类加载器去加载类,那么即使Class文件 相同,加载出来的结果也不一样,为了避免这种唯一性带来的不利,所以要给类加载器提供一种优先级层次,比如说,使用一个自定义的类加载去加载java.lang.Object,由于Object是由启动类加载器进行加载的(Object位于rt.jar中),所以根据双亲委派模型,最终不论哪种自定义的类加载器都会委派到启动类加载器进行加载,保证了Object类一定是由启动类加载器加载的,形成了一种绑定关系,那么Obejct类使用不同的自定义加载器去加载都能得到相同的结果

如果没有双亲委派模型,用户只要定义不同的自定义类加载器,去加载同一个全限定名的类,那么都会产生不同的结果

双亲委派模型对于保证Java程序的稳定运行极为重要,但它的实现却极为简单,下面就来看看这部分的代码,位于ClassLoader的loadClass方法上

protected Class<?> loadClass(String name, boolean resolve)

throws ClassNotFoundException

{

synchronized (getClassLoadingLock(name)) {

// First, check if the class has already been loaded

//检验是否已经被加载

Class<?> c = findLoadedClass(name);

if (c == null) {

//如果没被加载,开始加载动作

long t0 = System.nanoTime();

try {

//判断有没有父加载器

//前面提到过,是使用组合的方式来实现双亲委派模型的

if (parent != null) {

//如果有父加载器,递归执行父加载器的loadClass方法

c = parent.loadClass(name, false);

} else {

//如果没有父类加载器

//代表是要由启动类加载器进行加载

//前面也提到过,如果要将类的加载委托给启动类加载器进行

//就将自定义的启动类的parent设置为Null

c = findBootstrapClassOrNull(name);

}

} catch (ClassNotFoundException e) {

// ClassNotFoundException thrown if class not found

// from the non-null parent class loader

}

//如果上面的父加载器都加载失败

if (c == null) {

// If still not found, then invoke findClass in order

// to find the class.

long t1 = System.nanoTime();

//当前类加载器来进行处理

c = findClass(name);

// this is the defining class loader; record the stats

sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);

sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);

sun.misc.PerfCounter.getFindClasses().increment();

}

}

if (resolve) {

resolveClass©;

}

return c;

}

}

逻辑还是很简单的

  • 上锁

  • 判断有没有父类加载器

  • 有父类加载器,递归调用父类加载器的loadClass方法

  • 没有父类加载器,说明要委托给启动类加载器来进行加载

  • 如果父类加载器无法加载,返回依然为null,就会交由本类加载器来进行加载

破坏双亲委派模型

前面提到过,双亲委派模型不是一个具有强制约束性的模型,在Java世界中大部分的类加载器都遵循这个模型,但也有例外的情况,在此之前已经出现过几次大规模的破坏双亲委派模型情况了

第一次破坏双亲委派模型

第一次破快双亲委派模型是在双亲委派模型还没创建出来的时候,听起来比较绕,其实也不难理解,之所以要创建双亲委派模型,是因为之前的版本不是双亲委派模型嘛,之前不是双亲委派模型那么对应的就是被破坏的双亲委派模型嘛,这波是顶级理解。。。。。。。

第一次破坏双亲委派模型,是因为远古时期(JDK1.2之前)的JAVA已经有ClassLoader这个概念了,并且已经在用了,在此之前,用户去自定义类加载器是通过重写loadClass方法来决定的,这是因为虚拟机在进行类加载的时候会调用类加载器的私有方法,也就是loadClassInternal,而这个方法的唯一逻辑就是去调用自己的loadClass

在这里插入图片描述

双亲委派的机制肯定是包含在loadClass里面的,如果把loadClass给重写了,那么怎么进行双亲委派呢?(这就是破坏双亲委派的问题,只要重写了loadClass,双亲委派模型就会被破坏掉)

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助。

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
9YHV-1715790196929)]

[外链图片转存中…(img-GYtmWfkM-1715790196930)]

[外链图片转存中…(img-RmFM3tJ4-1715790196931)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,不论你是刚入门Java开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门!

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值