JVM之类加载器

文章目录

  • 版权声明
  • 类加载器
  • 类加载器的分类
    • 启动类加载器
    • 拓展类加载器&应用程序类加载器
  • 双亲委派机制
    • 解决三个问题
  • 打破双亲委派机制
    • 自定义类加载器
    • 案例演示
    • 线程上下文类加载器
    • 案例梳理
    • OSGi模块化

版权声明

  • 本博客的内容基于我个人学习黑马程序员课程的学习笔记整理而成。我特此声明,所有版权属于黑马程序员或相关权利人所有。本博客的目的仅为个人学习和交流之用,并非商业用途。
  • 我在整理学习笔记的过程中尽力确保准确性,但无法保证内容的完整性和时效性。本博客的内容可能会随着时间的推移而过时或需要更新。
  • 若您是黑马程序员或相关权利人,如有任何侵犯版权的地方,请您及时联系我,我将立即予以删除或进行必要的修改。
  • 对于其他读者,请在阅读本博客内容时保持遵守相关法律法规和道德准则,谨慎参考,并自行承担因此产生的风险和责任。本博客中的部分观点和意见仅代表我个人,不代表黑马程序员的立场。

类加载器

  • 类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。
  • 类加载器只参与加载过程中的字节码获取并加载到内存这一部分
    在这里插入图片描述
    在这里插入图片描述
  • 类加载器的应用场景
    在这里插入图片描述

类加载器的分类

  • 类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现的。
    -
  • 类加载器的设计JDK8和8之后的版本差别较大,JDK8及之前的版本中默认的类加载器有如下几种
    在这里插入图片描述
  • 类加载器的详细信息可以通过classloader命令查看:
    • classloader - 查看 classloader 的继承树,urls,类加载信息,使用 classloader 去getResource
    • 详细的指令教程请参考Arthas官网
[arthas@17048]$ classloadername                                       numberOfInstances  loadedCountTotalBootstrapClassLoader                       1                  2248com.taobao.arthas.agent.ArthasClassloader  1                  1351sun.misc.Launcher$ExtClassLoader           1                  47sun.reflect.DelegatingClassLoader          15                 15sun.misc.Launcher$AppClassLoader           1                  8
Affect(row-cnt:5) cost in 7 ms.
[arthas@17048]$ classloader -t
+-BootstrapClassLoader
+-sun.misc.Launcher$ExtClassLoader@7fe21cf8+-com.taobao.arthas.agent.ArthasClassloader@53e59711+-sun.misc.Launcher$AppClassLoader@18b4aac2
Affect(row-cnt:4) cost in 5 ms.
  • BootstrapClassLoader:c++编写的启动类加载器,位于虚拟机的源码中
    • numberOfInstances :1 类加载器的个数为一个
    • loadedCountTotal: 2248 共加载了2248个核心类
  • ArthasClassloader: ArthasClassloader工具的加载器
  • ExtClassLoader:拓展类加载器
  • DelegatingClassLoader: jdk底层实现用来提升反射效率的加载器
  • AppClassLoader:应用程序类加载器

启动类加载器

  • 启动类加载器(Bootstrap ClassLoader)是由Hotspot虚拟机提供的、使用C++编写的类加载器
  • 默认加载Java安装目录/jre/lib下的类文件,比如rt.jar,tools.jar,resources.jar等。
    在这里插入图片描述
  • 通过启动类加载器去加载用户jar包
    • 放入jre/lib下进行扩展:不推荐,尽可能不要去更改JDK安装目录中的内容,会出现即时放进去由于文件名不匹配的问题也不会正常地被加载
    • 使用参数进行扩展:推荐,使用-Xbootclasspath/a:jar包目录/jar包名 进行扩展

拓展类加载器&应用程序类加载器

  • 扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器

  • 它们的源码都位于sun.misc.Launcher中,是一个静态内部类,继承自URLClassLoader。通过目录或者指定jar包将字节码文件加载到内存中。
    在这里插入图片描述

  • 扩展类加载器(Extension Class Loader)是JDK中提供的、使用Java编写的类加载器

  • 默认加载Java安装目录/jre/lib/ext下的类文件

在这里插入图片描述

  • 通过扩展类加载器去加载用户jar包
    • 放入/jre/lib/ext下进行扩展:不推荐,尽可能不要去更改JDK安装目录中的内容
  • 使用参数进行扩展
    • 推荐,使用-Djava.ext.dirs=jar包目录 进行扩展,这种方式会覆盖掉原始目录,可以用;(windows):(macos/linux)追加上原始目录
  • 类加载器的加载路径可以先使用classloader -l查看类的hash值,然后通过classloader –c hash值 查看
    在这里插入图片描述

双亲委派机制

  • 由于Java虚拟机中有多个类加载器,那么一个类到底由哪个加载器进行加载?
  • 解决方案:双亲委派机制 双亲委派机制的核心是解决一个类到底由谁加载的问题
    在这里插入图片描述
  • 每个Java实现的类加载器中保存了一个成员变量叫“父”(Parent)类加载器,可以理解为它的上级,并不是继承关系。
    在这里插入图片描述
  • 启动类加载器使用C++编写,没有上级类加载器
  • 应用程序类加载器的parent父类加载器是扩展类加载器,而扩展类加载器的parent是空。扩展类加载器(Extension ClassLoader)在类加载过程中会委托给启动类加载器(Bootstrap ClassLoader)。这是因为Java的类加载器遵循所谓的"双亲委派模型"。
图1 真实的父类关系
图2 双亲委派机制中的逻辑关系
  • 尽管在类加载器的层次结构中,扩展类加载器的父类加载器是启动类加载器,但实际上,在类的加载过程中,应用类加载器的父类加载器是扩展类加载器,扩展类加载器没有父类加载器,但会委派给启动类加载器加载。这就是通常说扩展类加载器的父类加载器是启动类加载器。
  • 类加载器的继承关系可以通过classloader –t 查看
  • 双亲委派机制:当一个类加载器收到类加载请求时,它并不会尝试立即加载这个类,而是把这个请求委派给父类加载器。这样,每个类加载请求都会传递到启动类加载器,只有当父类加载器无法处理这个请求时(例如,类不在它的搜索路径中),子类加载器才会尝试自己去加载。

  • 双亲委派机制口诀:自底向上查找是否加载过,再由顶向下进行加载。
    在这里插入图片描述
    在这里插入图片描述

请添加图片描述
在这里插入图片描述

解决三个问题

  1. 重复的类:如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载?
    • 启动类加载器加载,根据双亲委派机制,它的优先级是最高的
  2. String类覆盖问题:在自己的项目中去创建一个java.lang.String类,会被加载吗?
    • 不能,会交由启动类加载器加载在rt.jar包中的String类
  3. 类加载器的关系:这几个类加载器彼此之间存在关系吗?
    • 应用类加载器的父类加载器是扩展类加载器,扩展类加载器没有父类加载器,但是会委派给启动类加载器加载

打破双亲委派机制

  • 打破双亲委派机制的三种方式
  1. 自定义类加载器
    • 自定义类加载器并且重写loadClass方法,就可以将双亲委派机制的代码去除
  2. 线程上下文类加载器
    • Tomcat通过这种方式实现应用之间类隔离
  3. Osgi框架的类加载器
    • 历史上Osgi框架实现了一套新的类加载器机制,允许同级之间委托进行类的加载

自定义类加载器

  • 一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类,Tomcat要保证这两个类都能加载并且它们应该是不同的类。
  • 如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载。
    在这里插入图片描述
  • Tomcat使用了自定义类加载器来实现应用之间类的隔离。每一个应用会有一个独立的类加载器加载对应的类。
    在这里插入图片描述

  1. 先来分析ClassLoader的原理,ClassLoader中包含了4个核心方法
  2. 双亲委派机制的核心代码就位于loadClass方法中
    在这里插入图片描述
public Class<?> loadClass(String name) throws ClassNotFoundException {return this.loadClass(name, false); //resolve
}protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {//加锁 保证只有一个线程加载类synchronized(this.getClassLoadingLock(name)) {//查询当前类加载器是否加载过需要的类,true 返回类对象 否则,返回nullClass<?> c = this.findLoadedClass(name);if (c == null) {long t0 = System.nanoTime();//向逻辑父类委派加载任务try {//直到委派到顶级父类——Extension,进入else分支if (this.parent != null) {c = this.parent.loadClass(name, false);} else {//调用启动类加载器,进行加载,无法加载返回nullc = findBootstrapClassOrNull(name);}} catch (ClassNotFoundException var10) {}//所有类加载器加载失败,使用本类加载器进行加载if (c == null) {long t1 = System.nanoTime();c = this.findClass(name);PerfCounter.getParentDelegationTime().addTime(t1 - t0);PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);PerfCounter.getFindClasses().increment();}}if (resolve) {this.resolveClass(c);}return c;}
}final Class<?> loadClass(Module module, String name) {synchronized(this.getClassLoadingLock(name)) {Class<?> c = this.findLoadedClass(name);if (c == null) {c = this.findClass(module.getName(), name);}return c != null && c.getModule() == module ? c : null;}
}
//读取二进制数据
protected Class<?> findClass(String name) throws ClassNotFoundException {throw new ClassNotFoundException(name);
}protected Class<?> findClass(String moduleName, String name) {if (moduleName == null) {try {return this.findClass(name);} catch (ClassNotFoundException var4) {}}return null;
}
//类名校验 将字节码信息加载到虚拟机内存中
protected final Class<?> defineClass(byte[] b, int off, int len) throws ClassFormatError {return this.defineClass((String)null, b, off, len, (ProtectionDomain)null);
}protected final Class<?> defineClass(String name, byte[] b, int off, int len) throws ClassFormatError {return this.defineClass(name, b, off, len, (ProtectionDomain)null);
}protected final void resolveClass(Class<?> c) {if (c == null) {throw new NullPointerException();}
}
  • 阅读双亲委派机制的核心代码,分析如何通过自定义的类加载器打破双亲委派机制
  • 打破双亲委派机制的核心就是将下边这一段代码重新实现
//parent等于null说明父类加载器是启动类加载器,直接调用findBootstrapClassOrNull
//否则调用父类加载器的加载方法
if (parent != null) {c = parent.loadClass(name, false);} else {c = findBootstrapClassOrNull(name);}
//父类加载器爱莫能助,我来加载!
if (c == null) c = findClass(name);

案例演示

public class CustomClassLoader extends ClassLoader {public CustomClassLoader(ClassLoader parent) {super(parent);}@Overridepublic Class<?> loadClass(String name) throws ClassNotFoundException {// 简单的例子, 只处理自定义的类if (name.startsWith("com.mydomain")) {return findClass(name);}// 其他的类还是采用双亲委派机制加载return super.loadClass(name);}@Overrideprotected Class<?> findClass(String name) throws ClassNotFoundException {try {byte[] bytes = loadClassData(name);return defineClass(name, bytes, 0, bytes.length);} catch (IOException e) {throw new ClassNotFoundException("Cannot load class " + name, e);}}private byte[] loadClassData(String name) throws IOException {// 根据类名加载类的二进制数据. 假设所有类都在一个固定的目录下Path path = Paths.get("classes", name.replace('.', '/') + ".class");return Files.readAllBytes(path);}}
  1. 自定义类加载器默认父类是AppClassLoader
  • 以Jdk8为例,ClassLoader类中提供了构造方法设置parent的内容
    在这里插入图片描述
  • 这个构造方法由另外一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的是AppClassLoader。
    在这里插入图片描述
  • 两个自定义类加载器加载相同限定名的类,不会冲突
    • 在同一个Java虚拟机中,只有相同类加载器+相同的类限定名才会被认为是同一个类

  • 正确的去实现一个自定义类加载器的方式是重写findClass方法,这样不会破坏双亲委派机制
    在这里插入图片描述

线程上下文类加载器

  • 利用上下文类加载器加载类,比如JDBC和JNDI等
  • JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql驱动、oracle驱动。
    在这里插入图片描述
  1. DriverManager类位于rt.jar包中,由启动类加载器加载
    在这里插入图片描述

  2. 依赖中的mysql驱动对应的类,由应用程序类加载器来加载。
    在这里插入图片描述

  • DriverManager属于rt.jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制
    在这里插入图片描述
  • DriverManage使用SPI机制,最终加载jar包中对应的驱动类
    在这里插入图片描述
  • SPI中获取到应用程序类加载器
    • SPI中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器
      在这里插入图片描述

案例梳理

⚫1. 启动类加载器加载DriverManager。
⚫ 2. 在初始化DriverManager时,通过SPI机制加载jar包中的myql驱动。
⚫ 3. SPI中利用了线程上下文类加载器(应用程序类加载器)去加载类并创建对象。

  • 由启动类加载器加载的类,委派应用程序类加载器去加载类的方式,打破了双亲委派机制
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

OSGi模块化

  • OSGi模块化框架。它存在同级之间的类加载器的委托加载。OSGi还使用类加载器实现了热部署的功能。
  • 热部署指的是在服务不停止的情况下,动态地更新字节码文件到内存中
    在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/191810.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

mac 安装使用svn教程

mac 安装使用svn教程 一、安装Homebrew 要在Mac OS上安装SVN&#xff0c;首先需要安装Homebrew。Homebrew是一个流行的包管理器&#xff0c;因此我们将使用它来安装SVN。 /bin/zsh -c "$(curl -fsSL https://gitee.com/cunkai/HomebrewCN/raw/master/Homebrew.sh)"…

介绍 Docker 的基本概念和优势,以及在应用程序开发中的实际应用

Docker是一种基于容器的虚拟化技术&#xff0c;它允许开发者将应用程序及其依赖项打包到一个轻量级容器中&#xff0c;然后在任何可用的开发、测试和生产环境中进行部署和运行。 下面是Docker的基本概念和优势&#xff1a; 容器&#xff1a;Docker容器是一种独立运行的软件包&a…

【开源】基于Vue.js的校园失物招领管理系统的设计和实现

目录 一、摘要1.1 项目介绍1.2 项目详细录屏 二、研究内容2.1 招领管理模块2.2 寻物管理模块2.3 系统公告模块2.4 感谢留言模块 三、界面展示3.1 登录注册3.2 招领模块3.3 寻物模块3.4 公告模块3.5 感谢留言模块3.6 系统基础模块 四、免责说明 一、摘要 1.1 项目介绍 基于Vue…

数列计算

题目描述 有一列数是 : 请找出这个数列的规律&#xff0c;编写程序计算并输出这个数列的第项&#xff0c;要求是分数形式&#xff0c;并计算这个数列的前项和 ( 结果四舍五入保留两位小数 ) 输入格式 第一行仅有一个正整数 &#xff08;) 。 输出格式 共有 行&#xff0c;第一…

Java基础-基础语法

1、概述 一个 Java 程序可以认为是一系列对象的集合&#xff0c;而这些对象通过调用彼此的方法来协同工作。 对象&#xff1a;对象是类的一个实例&#xff0c;有状态和行为。例如&#xff0c;一条狗是一个对象&#xff0c;它的状态有&#xff1a;颜色、名字、品种&#xff1b;…

Java之“数字困境”:资产管理项目中的Bug追踪与启示

目录 1 前言2 问题的发现3 调试的开始4 深入调试5 调试心得与反思6 结语 1 前言 在程序员的日常工作中&#xff0c;我们时常面对各种令人头疼的问题&#xff0c;其中最令人崩溃的瞬间之一&#xff0c;就是当我们花费大量时间追踪一个看似复杂的bug&#xff0c;最终发现问题的根…

蒙特卡洛树搜索(Monte Carlo Tree Search)揭秘

一. 什么是蒙特卡洛树搜索 蒙特卡洛树搜索(MCTS)是一种启发式搜索算法&#xff0c;一般用在棋牌游戏中&#xff0c;如围棋、西洋棋、象棋、黑白棋、德州扑克等。MCTS与人工神经网络结合&#xff0c;可发挥巨大的作用&#xff0c;典型的例子是2016年的AlphaGo&#xff0c;以4:1…

JAVA集合学习

一、结构 List和Set继承了Collection接口&#xff0c;Collection继承了Iterable Object类是所有类的根类&#xff0c;包括集合类&#xff0c;集合类中的元素通常是对象&#xff0c;继承了Object类中的一些基本方法&#xff0c;例如toString()、equals()、hashCode()。 Collect…

实战Leetcode(五)

Practice makes perfect&#xff01; 实战一&#xff1a; 思路&#xff1a;我们要用复制的节点来组成一个新的链表&#xff0c;而原链表的节点随机指向其中一个节点&#xff0c;我们首先给每一个节点都复制并且插入到原来节点的后面&#xff0c;然后用复制的节点指向我们原来节…

物理问题中常见的分析问题----什么样的函数性质较好

物理问题中常见的积分符号位置交换问题 重极限与累次极限 高数下的定义 累次极限&#xff1a;求极限时需要遵循一定的顺序重极限&#xff1a;任意方向趋于的极限 两者之间的关系&#xff1a; 两者没啥关系存在累次极限存在而不相等的函数...... 求和符号与积分符号互换--逐项积…

RK3568笔记五:基于Yolov5的训练及部署

若该文为原创文章&#xff0c;转载请注明原文出处。 一. 部署概述 环境&#xff1a;Ubuntu20.04、python3.8 芯片&#xff1a;RK3568 芯片系统&#xff1a;buildroot 开发板&#xff1a;ATK-DLRK3568 开发主要参考文档&#xff1a;《Rockchip_Quick_Start_RKNN_Toolkit2_C…

专题知识点-二叉树-(非常有意义的一篇文章)

这里写目录标题 二叉树的基础知识知识点一(二叉树性质 )树与二叉树的相互转换二叉树的遍历层次优先遍历树的深度和广度优先遍历中序线索二叉树二叉树相关遍历代码顺序存储和链式存储二叉树的遍历二叉树的相关例题左右两边表达式求值求树的深度找数找第k个数二叉树非递归遍历代码…

C++ builder 常见问题汇总

1、CB静态编译设置 2、CB10.3设置经典编译器&#xff08;用于解决10.3弹出代码提示慢&#xff09; 3、CBuilder生成Release版本 &#xff1a; project->Options->CCompiler->Build Configuration 选择 Release project->Options->CLinker中取消Use dynamic RTL…

简单实现,在nodejs中简单使用kafka

什么是 Kafka Kafka 是由 Linkedin 公司开发的&#xff0c;它是一个分布式的&#xff0c;支持多分区、多副本&#xff0c;基于 Zookeeper 的分布式消息流平台&#xff0c;它同时也是一款开源的基于发布订阅模式的消息引擎系统。 Kafka 的基本术语 消息&#xff1a;Kafka 中的…

深入理解 Django 单元测试

概要 在现代软件开发流程中&#xff0c;单元测试是确保代码质量和可维护性的关键组成部分。对于使用 Django 框架的项目来说&#xff0c;Django 提供了一套强大的测试工具来帮助开发者编写和运行单元测试。本文将深入探讨 Django 中的单元测试&#xff0c;包括测试原理、编写测…

vue3 ref 与shallowRef reactive与shallowReactive

ref 给数据添加响应式&#xff0c;基本类型采用object.defineProperty进行数据劫持&#xff0c;对象类型是借助reactive 实现响应式&#xff0c;采用proxy 实现数据劫持&#xff0c;利用reflect进行源数据的操作 let country ref({count:20,names:[河南,山东,陕西],objs:{key…

19C进入数据库出现问号

问题情况如图所示&#xff1a; 解决方法&#xff1a; su - oracle echo "NLS_LANGAMERICAN_AMERICA.ZHS16GBK;export NLS_LANG" >> ~/.bash_profilesource ~/.bash_profileofile

《网络协议》05. 网络通信安全 · 密码技术

title: 《网络协议》05. 网络通信安全 密码技术 date: 2022-09-10 15:16:15 updated: 2023-11-12 07:03:52 categories: 学习记录&#xff1a;网络协议 excerpt: 网络通信安全&#xff08;ARP 欺骗&#xff0c;DoS & DDoS&#xff0c;SYN 洪水攻击&#xff0c;LAND 攻击&a…

activiti7审批驳回,控制变量无法覆盖,导致无限循环驳回,流程无法结束

项目开发过程中使用工作流&#xff0c;因此考虑使用activiti7做完工作流引擎。项目开发过程中&#xff0c;发现流程驳回时&#xff0c;再次执行流程&#xff0c;控制变量无法覆盖&#xff0c;导致无限循环驳回&#xff0c;流程无法结束。流程图如下图所示&#xff1a; 驳回控制…

数据结构 栈(C语言实现)

目录 1.栈的概念及结构2.栈的代码实现 1.栈的概念及结构 栈&#xff1a;一种特殊的线性表&#xff0c;其只允许在固定的一端进行插入和删除元素操作。进行数据插入和删除操作的一端 称为栈顶&#xff0c;另一端称为栈底。栈中的数据元素遵守后进先出LIFO&#xff08;Last In F…