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

JVM学习之路(3)类加载器

目录

什么是类加载器?

类加载器的应用场景

类加载器的分类

Arthas中类加载器相关的功能

启动类加载器

扩展类加载器和应用程序类加载器

双亲委派机制

双亲委派机制有什么用?

向上查找向下加载

双亲委派机制面试题

打破双亲委派机制

第一种:自定义类加载器:

第二种:线程上下文切换类加载器

第三种:OSGI 模块化

使用阿里arthas不停机解决线上问题(简单的了解)

JDK9之后的类加载器


欢迎关注我的博客!26届java选手,一起加油💘💦👨‍🎓😄😂

什么是类加载器?

类加载器(ClassLoader)是Java虚拟机提供给应用程序去实现获取类和接口字节码数据的技术。

类加载器只参与加载过程中的字节码获取并加载到内存这一部分。

类加载器的应用场景

企业级应用

  • SPI机制
  • 类的热部署
  • Tomcat类的隔离

大量的面试题

  • 什么是类的双亲委派机制
  • 打破类的双亲委派机制
  • 自定义类加载器

解决线上问题

  • 使用Arthas不停机解决线上故障

类加载器的分类

类加载器分为两类,一类是Java代码中实现的,一类是Java虚拟机底层源码实现的。(着重关注)。

类加载器的设计JDK8和8之后的版本差别较大,JDK8及之前的版本中默认的类加载器有如下几种。

classloader 的名字, 数量, 和加载了多少个类

Arthas中类加载器相关的功能

类加载器的详细信息可以通过 classloader 命令查看:

classloader - 查看 classloader 的继承树,urls,类加载信息,使用 classloader 去 getResource 

在java程序所在文件夹用cmd启动arthas

输入3进入我们想看的java程序内部

输入classloader,可以查看到有多少个类加载器。

启动类加载器

启动类加载器,由于底层是c++,程序员无法修改和扩展,重点在这个加载器的作用

  • 启动类加载器 (Bootstrap ClassLoader)  是由Hotspot虚拟机提供的、使用C++编写的类加载器
  • 默认加载Java安装目录/jre/lib下的类文件,比如rtjartools,jar,resources.jar等、

我们也可以在自己的idea中查看到这些被启动类加载器加载的jar包

在里面可以找到我们常用的String类,被启动类加载器把这个类加载进来,我们才可以使用。

运行以下程序,我们想要获取String的classloader对象:

但是无法获取:

原因s :启动类加载器不存在于java代码中, 而位于虚拟机中,所以没办法通过代码获取到底层的类加载器,所以是不允许获取的。当我们获取的类加载器为空,有种情况就是这是启动类加载器了。

用arthas的sc -d + 类名 的方式来查看类加载器,发现确实是空的

先让启动类加载器加载我们自己的类?

使用参数进行扩展,我们编写一个A类,package成jar包

打包的jar包如下:

idea中添加配置

用class.forname的方式来获取这个A类,最后也是成功初始化,这就是通过参数的方式来让启动类加载器来加载对应的jar包

再打印启动类加载器输出的依然是null,证明现在还是在使用启动类加载器。

扩展类加载器和应用程序类加载器

  • 扩展类加载器和应用程序类加载器都是JDK中提供的、使用Java编写的类加载器
  • 它们的源码都位于sun.misc.Launcher中,是一个静态内部类。继承自URLClassLoader。具备通过目录或者指定jar包将字节码文件加载到内存中。

通用但不重要。

  • 扩展类加载器(Extension Class Loader) 是JDK中提供的使用Java编写的类加载器
  • 默认加载Java安装目录/ire/lib/ext下的类文件

验证 上图中的nashorn确实是由扩展类加载器加载的

如果我们自己也写了一些通用但不重要的类文件,能不能让扩展类加载器帮助我们加载呢?

是可以的,我们依然使用参数进行扩展:添加如下参数

验证:可以看到应用程序加载类既可以加载我们自己创建的类,也能加载maven中包含的一些类。

在arthas中用classloader -l 获取到每一个类加载器和对应的哈希码,然后用这个哈希码使用 classloader -c (哈希码)就可以看到这个类加载器加载的jar包,如下图,我们可以看到扩展类加载器加载的的的确确路径都是在ext下的jar包

        接下来查看应用程序类加载器,里面既有maven依赖的jar包,又有我们自己的target/classes,但是同时还有启动类加载器和扩展类加载器,这个跟我们想象中有点不一样,应用程序类加载器覆盖的范围有点大了,这样不会导致重复加载吗? 放在后面讲。

双亲委派机制

由于Java虚拟机中有多个类加载器,双亲委派机制的核心是解决一个类到底由谁加载的问题。

双亲委派机制有什么用?

  • 1.保证类加载的安全性:通过双亲委派机制避免恶意代码替换JDK中的核心类库,比如java.Lang.string,确保核心类库的完整性和安全性。
  • 2.避免重复加载:双亲委派机制可以避免同一个类被多次加载。

向上查找向下加载

双亲委派机制指的是: 当一个类加载器接收到加载类的任务时,会自底向上查找是否加载过再由顶向下进行加载。

向上查找看是否由加载器加载过这个类, 有就直接返回。

举例:这个类在class path中,向下加载

双亲委派机制总结:有两个流程,首先由当前执行任务的加载器开始,向上查找这个类有没有被加载过,如果发现已经被加载过,那么加载流程就结束了,把这个类的class对象返回即可,但是如果所有类加载器都没有加载过,那么就会从顶向下尝试去进行加载,加载顺序就是启动类加载器,扩展类加载器,以及应用程序类加载器,如果这当中有一个类加载器发现这个类在自己的加载路径中,就会选择去加载这个类,这样加载过程就结束了。

重复的类
如果一个类重复出现在三个类加载器的加载位置,应该由谁来加载?

答:启动类加载器加载,根据双亲委派机制,它的优先级是最高的 

String类能覆盖吗
在自己的项目中去创建一个java.lang.String类,会被加载吗?

答:不能,会返回启动类加载器加载在rt.jar包中的string类。String类在查找的时候就被发现被启动类加载器加载了,所以直接返回了,自己写的String类就不能被加载了。

在Java中如何使用代码的方式去主动加载一个类呢?

方式1:使用Class.forName方法,使用当前类的类加载器去加载指定的类。

方式2:获取到类加载器,通过类加载器的loadClass方法指定某个类加载器加载。

例如:

演示:先获取到classloader, 然后打印看到是应用程序类加载器,然后用这个启动器去加载String类,发现输出的不是应用程序加载类,而是null,证明这是启动类加载器。

这样就能避免启动类加载器中的类被人为覆盖,保证了安全性。

  • 应用程序类加载器的 parent 父类加载器是扩展类加载器,而扩展类加载器的parent是空,但是在代码逻辑上,扩展类加载器依然会把启动类加载器当成父类加载器处理。
  • 启动类加载器使用C++编写,没有父类加载器。

在arthas中查看,启动类加载器和扩展类加载器虽然是平级的,因为扩展类加载器指向null,但是逻辑上,启动类加载器是他的父类。

双亲委派机制面试题

类的双亲委派机制是什么?

1、当一个类加载器去加载某个类的时候,会自底向上查找是否加载过,如果加载过就直接返回,如果一直到最顶层的类加载器都没有加载,再由顶向下进行加载。
2、应用程序类加载器的父类加载器是扩展类加载器,扩展类加载器的父类加载器是启动类加载器
3、双亲委派机制的好处有两点: 第一是避免恶意代码替换JDK中的核心类库,比如java.lang.String,确保核心类库的完整性和安全性。第二是避免一个类重复地被加载。

打破双亲委派机制

有如下三种方法:

前两种比较重要,第三种了解:

第一种:自定义类加载器:

  • 一个Tomcat程序中是可以运行多个Web应用的,如果这两个应用中出现了相同限定名的类,比如Servlet类Tomcat要保证这两个类都能加载并且它们应该是不同的类。
  • 如果不打破双亲委派机制,当应用类加载器加载Web应用1中的MyServlet之后,Web应用2中相同限定名的MyServlet类就无法被加载了

Tomcat使用了自定义类加载器来实现应用之间类的隔离每一个应用会有一个独立的类加载器加载对应的类

如何打破一个双亲委派机制?

  • 先来分析ClassLoader的原理,ClassLoader中包含了4个核心方法
  • 双亲委派机制的核心代码就位于loadClass方法中。

看了原理之后 , 如何打破?

双亲委派机制的核心代码

打破:重写loadClass方法,

类的加载器变成我们自定义的了。

看看自定义加载器的父类加载器:

以Jdk8为例,ClassLoader类中提供了构造方法设置parent的内容:

也即是自定义的类加载器默认的父类加载器是应用程序类加载器。

这个构造方法由另外一个构造方法调用,其中父类加载器由getSystemClassLoader方法设置,该方法返回的是AppClassLoader

问题
两个自定义类加载器加载相同限定名的类,不会冲突吗?
不会冲突,在同一个Java虚拟机中,只有相同类加载器+相同的类限定名才会被认为是同一个类
在Arthas中使用sc-d类名的方式查看具体的情况

验证:不相等

正确的去实现一个自定义类加载器的方式是重写findClass方法,这样不会破坏双亲委派机制。

第二种:线程上下文切换类加载器

以JDBC举例:JDBC中使用了DriverManager来管理项目中引入的不同数据库的驱动,比如mysql驱动、oracle驱动。

DriverManager类位于rtjar包中,由启动类加载器加载

依赖中的mysql动对应的类,由应用程序类加载器来加载

DriverManager属于rt;jar是启动类加载器加载的。而用户jar包中的驱动需要由应用类加载器加载,这就违反了双亲委派机制。

问题是DriverManager怎么知道jar包中要加载的驱动在哪儿?

JDBC案例之SPI机制

  • spi全称为(Service Provider Interface),是JDK内置的一种服务提供发现机制。
  • spi的工作原理: 1.在ClassPath路径下的META-INF/services文件夹中,以接口的全限定名来命名文件名,对应的文件里面写该接口的实现。

spi机制流程分两步,第一部分是需要在驱动的架构中去暴露出要让别人去加载哪个类,比如com.mysql.cj.jdbc

然后放到固定的文件中, 然后在DriverManager中就会主动的使用ServiceLoader 去加载文件中的类名,并且使用类加载器去加载对应的类并创建对象。

DriverManage使用SPI机制,最终加载jar包中对应的驱动类

SPI中使用了线程上下文中保存的类加载器进行类的加载,这个类加载器一般是应用程序类加载器。

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

 JDBC案例中真的打破了双亲委派机制吗? 对此有两种看法

  • 1、打破了双亲委派机制 :这种由启动类加载器加载的类,委派应用程序类加载器去加载类的方式
  • 2、没有打破双亲委派机制 :类加载流程中,没有违反双亲委派机

第三种:OSGI 模块化

历史上,OSGi模块化框架。它存在同级之间的类加载器的委托加载。OSGi还使用类加载器实现了热部署的功能。

使用阿里arthas不停机解决线上问题(简单的了解)

背景:

小李的团队将代码上线之后,发现存在一个小bug,但是用户急着使用,如果重新打包再发布需要一个多小时的时间,所以希望能使用arthas尽快的将这个问题修复。

思路:

  • 1.在出问题的服务器上部署一个 arthas,并启动
  • 2. jad --source-only 类全限定名 > 目录/文件名.     javajad 命令反编译,然后可以用其它编译器,比如 vim 来修改源码
  • 3.mc -c 类加载器的hashcode 目录/文件名.java -d 输出目录    mc命令用来编译修改过的代码
  • 4.retransform class文件所在目录/xxx.class   用retransform命令加载新的字节码

注意事项:

  • 1、程序重启之后,字节码文件会恢复,除非将class文件放入jar包中进行更新
  • 2、使用retransform不能添加方法或者字段,也不能更新正在执行中的方法

JDK9之后的类加载器

JDK8及之前的版本中,扩展类加载器和应用程序类加载器的源码位于 rt.jar 包中的sun.misc.Launcherjava。

由于JDK9引入了module的概念,类加载器在设计上发生了很多变化,变化如下:

1.启动类加载器使用Java编写,位于jdk.internal.loaderClassLoaders类中Java中的BootClassLoader继承自BuiltinClassLoader实现从模块中找到要加载的字节码资源文件。启动类加载器依然无法通过java代码获取到,返回的仍然是null,保持了统一。

2、扩展类加载器被替换成了平台类加载器 (Platform Class Loader)。平台类加载器遵循模块化方式加载字节码文件,所以继承关系从URLClassLoader变成了BuiltinClassLoader,BuiltinClassLoader实现了从模块中加载字节码文件。平台类加载器的存在更多的是为了与老版本的设计方案兼容,自身没有特殊的逻辑。


http://www.mrgr.cn/news/58148.html

相关文章:

  • 六,Linux基础环境搭建(CentOS7)- 安装HBase
  • AI智能电销机器人有什么功能?语音机器人系统搭建
  • python表格处理prettytable vs pandas
  • 笛卡尔空间内的阻抗控制
  • 使用ceph-csi把ceph-fs做为k8s的storageclass使用
  • tomcat安装启动配置以及乱码问题
  • 正则表达式基础知识
  • 【Docker】Dockerfile 用于组装镜像的指令都有啥?
  • Robot Framework接口自动化测试案例
  • Shell 编程-Shell 函数你学会了吗?
  • 【含开题报告+文档+PPT+源码】社区医院预约挂号看病系统的设计与实现
  • python回调函数概念及应用场景举例
  • 于Java语言 Netty通讯框架的云块充协议1.5_云快充协议1.6_云快充协议1.4_云快充协议
  • Flutter TextField和Button组件开发登录页面案例
  • 【赵渝强老师】Hive的内部表与外部表
  • TreeMap详解
  • 产品推介——LSOP4晶体管光耦KL101X
  • web 请求日志追踪(traceID)提升运维效率
  • Nexpose 6.6.274 发布下载,新增功能概览
  • 华为OD机试 - 创建二叉树(Java 2024 E卷 200分)
  • 基于Java+SpringBoot+Vue的宠物咖啡馆平台的设计与实现
  • JavaScript 中四种常见的数据类型判断方法
  • 【深度学习中的注意力机制10】11种主流注意力机制112个创新研究paper+代码——交叉注意力(Cross-Attention)
  • 附录章节:SQL标准与方言对比
  • 【已解决】【hadoop】如何解决Hive连接MySQL元数据库的依赖问题
  • 【C++】位图