当前位置: 首页 > news >正文

JVM—虚拟机类加载器

参考资料:深入理解Java虚拟机:JVM高级特性与最佳实践(第3版)周志明

1. 类加载器

JVM设计团队有意把类加载阶段中的 “通过一个类的全限定名来获取该类的二进制字节流” 这个动作放到JVM外部实现,这个动作的代码称为类加载器。

1.1 类与类加载器

类加载器虽然只作用于实现类的加载动作,但是在Java程序中起到的作用远超类加载阶段。

  • 对于任意一个类,都必须由加载它的类加载器和这个类本身一起共同确立在其Java虚拟机中的唯一性,也就是比较两个类是否“相等”,只有两个类是同一个类加载器加载的前提下才有意义。

  • 同一个Class文件,被同一个Java虚拟机加载,只要类加载器不同,那么这两个类必然不相等。

这里的“相等”,包括Class对象的equals、isAssignableFrom、isInstance方法的返回结果。也包括了instanceof关键字的对象关系判定。

package JvmTest;import java.io.IOException;
import java.io.InputStream;public class ClassLoaderTest {public static void main(String[] args) throws Exception {ClassLoader myLoader = new ClassLoader() {@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {try {String filename = name.substring(name.lastIndexOf(".") + 1) + ".class";InputStream is = getClass().getResourceAsStream(filename);if (is == null) {return super.loadClass(name);}byte[] b = new byte[is.available()];is.read(b);return defineClass(name, b, 0, b.length);} catch (IOException e) {throw new ClassNotFoundException(name);}}};//loadClass参数代表类的全限定名Object obj = myLoader.loadClass("JvmTest.ClassLoaderTest");System.out.println(obj.getClass());System.out.println(obj instanceof ClassLoaderTest);}
}
// 运行结果:
class java.lang.Class
false

1.2 双亲委派模型

从JVM角度来讲只有两种不同的类加载器:一种是启动类加载器,另一个是其他的所有类加载器(都是由Java语言实现,并且全部继承自抽象类ClassLoader

1.2.1 三层类加载器

1.2.1.1 启动类加载器
  • 负责加载<JAVA_HOME>\lib目录的.jar文件

  • 负责加载JRE核心库,这些库是本地代码实现,无法在java中访问到。

  • 启动类加载器无法被java程序直接引用,在自定义类加载器时,将需要加载请求委派给启动类加载器处理。

1.2.1.2 扩展类加载器
  • 负责加载<JAVA_HOME>\lib/ext目录下的所有类库,这些类库提供了对核心类库的扩展。

  • 扩展类加载器由java实现的,因此可以直接在程序中使用扩展类加载器来加载Class文件。

1.2.1.3 应用程序类加载器
  • 应用程序类加载器用于加载应用程序的类和资源(通常从CLASSPATH中指定的目录和JAR文件中加载)。

  • 负责加载用户路径上的所有类库,同样可以直接在代码中使用这个类加载器。

  • 如果没有显示的定义自己的类加载器,那么这个就是应用程序默认的类加载器

1.2.2 双亲委派模型

如图展示的各类加载器之间的层次关系被称为类加载器的“双亲委派模型”,双亲委派模型除了要求有顶层的启动类加载器,并且其余的类都有自己的父类加载器。

不过各类加载器之间不是通过继承关系实现,而是通过组合的关系实现复用。

双亲委派的优点

  • 使用双亲委派模型组织类加载器之间的关系,好处在于Java中的类也享受到了这种优先的层级关系,无论加载哪个类最终都是最顶层启动类加载器加载。

  • 可以保证各种类加载器环境都是同一个类。

  • 如果没有这种机制,例如有一个系统Object和用户编写的Object,会出现重名导致编译通过,但永远无法加载运行

1.2.2.1 双亲委派模型的实现
protected synchronized Class<?> loadClass(String name, boolean resolve) throwsClassNotFoundException {// 首先,检查请求的类是否已经被加载过了Class c = findLoadedClass(name);if (c == null) {try {if (parent != null) {// 传递给父类c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException e) {// 如果父类加载器抛出 ClassNotFoundException// 说明父类加载器无法完成加载请求}if (c == null) {// 在父类加载器无法加载时// 再调用本身的 findClass 方法来进行类加载c = findClass(name);}}if (resolve) {resolveClass(c);}return c;
}

先检查请求加载的类型是否已经被加载过,若没有则调用父加载器的 loadClass()方法,若父加载器为空则默认使用启动类加载器作为父加载器。假如父类加载器加载失败,抛出ClassNotFoundException 异常的话,才调用自己的findClass()方法尝试进行加载。

1.3 破坏双亲委派模型

直到Java模块化的出现之前,双亲委派继承出现过3次较大规模的被“破坏”。

第一次

  • 父类loadClass可能被覆盖

在JDK1.2之前没有双亲委派模型,在此之后为了兼容以前的代码,无法避免loadClass()被子类覆盖的可能,增加了新的方法findClass()来完成加载,引导用户重写这个方法而不是在loadClass中编写代码。

第二次

  • 模型缺陷,双亲委派很好的解决了各类加载器之间的协作时基础类型的一致性问题(越基础的类使用越上层类加载器完成),但是基础类经常被用户继承,调用Api

  • 如果基础类型回调用户代码就会出现问题。

Java设计团队设计了一个线程上下文类加载器,这个类加载器可以通过Thread类的setContextClassLoader()来设置。

它使得父类加载器可以去请求子类加载器完成类加载行为,实际上打破了双亲委派的层次结构。

第三次

  • 由于用户追求程序的动态性导致的,企图实现类似于热插拔的效果,在不重启设备的情况下。

使用OSGi实现模块化热部署,它自定义了类加载机制的实现,每个应用模块(Bundle)都有一个自己的类加载器,当需要更换Bundle时,就连同类加载器一起换掉实现代码的热替换。

但是,OSGi不再使用双亲委派机制,而是发展成一种更加复杂的网状结构。

1.4 Java模块化系统

JDK9引入了模块化系统,为了实现模块化的关键目标—可配置的封装隔离机制。Java对类加载机制也做出了调整。

1.4.1 什么是可配置的封装隔离机制

是一种软件设计模式,它通过将系统中的相关组件(类、函数、变量等)封装到一个独立的容器中,实现组件之间的逻辑隔离。 这个容器可以被动态地配置和修改,实现不同的功能需求。

1.4.2 可配置的封装隔离机制解决的问题

  1. 解决了JDK9之前基于类路径(ClassPath)来查找依赖的可靠性问题。启用了模块化进行封装,模块声明对其他模块的依赖,使得JVM在启动时就能验证应用程序的依赖关系是否完备。

  2. 解决了原来路径上跨JAR文件的public类型的可访问性问题,JDK9的public不意味着程序的所有代码都可以访问,模块提供了更加精细化的访问(必须声明哪些public类型可以被哪些模块访问)。

1.4.3 模块下的类加载器

为了保证兼容性,JDK9没有动摇三层类加载器架构和双亲委派模型,为了保证模块化正常运行,模块化的类加载器发生了一些变化。

  • 首先扩展类加载器平台类加载器取代。

由于整个JDK都基于模块化进行构建,其中Java类库天然满足可扩展的需求,自然不需要扩展类加载器。

  • 平台类加载器和应用程序类加载器继承关系发生变化。

  1. 都不再派生自java.net.URLClassLoader,现在三大加载器全部继承自BuiltinCLassLoader

  2. BuiltinCLassLoader中实现了新模块架构下类如何从模块中加载的逻辑。

  3. 即使现在启动类加载器有了BuiltinCLassLoader这样的java类,但是获取启动类加载器也会返回null代替

1.4.3.1 新的双亲委派机制

  • 当平台以及应用程序类加载器收到类加载请求,在委派给父类之前不会立即委派。

  • 而是先判断类是否可以归属到某个系统模块,如果存在归属关系则优先委派给负责该模块的加载器。

  • 这算是双亲委派机制的第四次破坏。

相关文章:

  • 北京网站建设多少钱?
  • 辽宁网页制作哪家好_网站建设
  • 高端品牌网站建设_汉中网站制作
  • 机器学习练手(三):基于决策树的iris 多分类和波士顿房价预测
  • 华为的流程体系
  • 【大模型】【面试】独家总结表格
  • ISA95-Part8-错误处理的设计与集成
  • 【二】测试工具
  • 21天学通C++:理解函数对象、Lambda表达式
  • 微信小程序css中配置了文字超出一行或两行则显示省略号对纯数字或纯字母或小数点无效的解决办法
  • C Primer Plus 第5章——第一篇
  • C++ | Leetcode C++题解之第318题最大单词长度乘积
  • git clone private repo
  • 【iOS】多界面传值
  • pycharm中安装、使用扩展工具,以QT Designer为例
  • Centos 8系统xfs文件系统类型进行扩容缩容 (LVM)
  • C++初学者指南-5.标准库(第二部分)--更改元素算法
  • 【Spring Boot】用 Spring Security 实现后台登录及权限认证功能
  • JS中 map, filter, some, every, forEach, for in, for of 用法总结
  • __proto__ 和 prototype的关系
  • 《Javascript高级程序设计 (第三版)》第五章 引用类型
  • Apache的80端口被占用以及访问时报错403
  • CSS 三角实现
  • css布局,左右固定中间自适应实现
  • HashMap ConcurrentHashMap
  • JS变量作用域
  • k个最大的数及变种小结
  • LeetCode29.两数相除 JavaScript
  • 订阅Forge Viewer所有的事件
  • 关于List、List?、ListObject的区别
  • 每个JavaScript开发人员应阅读的书【1】 - JavaScript: The Good Parts
  • 前端每日实战:70# 视频演示如何用纯 CSS 创作一只徘徊的果冻怪兽
  • 数据库写操作弃用“SELECT ... FOR UPDATE”解决方案
  • 无服务器化是企业 IT 架构的未来吗?
  • 一个6年java程序员的工作感悟,写给还在迷茫的你
  • 云栖大讲堂Java基础入门(三)- 阿里巴巴Java开发手册介绍
  • 终端用户监控:真实用户监控还是模拟监控?
  • MiKTeX could not find the script engine ‘perl.exe‘ which is required to execute ‘latexmk‘.
  • ​Python 3 新特性:类型注解
  • ​TypeScript都不会用,也敢说会前端?
  • # 利刃出鞘_Tomcat 核心原理解析(七)
  • ## 临床数据 两两比较 加显著性boxplot加显著性
  • ()、[]、{}、(())、[[]]命令替换
  • (007)XHTML文档之标题——h1~h6
  • (35)远程识别(又称无人机识别)(二)
  • (4.10~4.16)
  • (CPU/GPU)粒子继承贴图颜色发射
  • (WSI分类)WSI分类文献小综述 2024
  • (附源码)node.js知识分享网站 毕业设计 202038
  • (附源码)spring boot北京冬奥会志愿者报名系统 毕业设计 150947
  • (附源码)springboot掌上博客系统 毕业设计063131
  • (回溯) LeetCode 46. 全排列
  • (算法)大数的进制转换
  • (一)Docker基本介绍
  • (转)程序员技术练级攻略
  • (转)总结使用Unity 3D优化游戏运行性能的经验
  • (最简单,详细,直接上手)uniapp/vue中英文多语言切换
  • .equal()和==的区别 怎样判断字符串为空问题: Illegal invoke-super to void nio.file.AccessDeniedException