问题(十五)性能分析组件类加载问题

一、引言

        最近作者在做性能分析服务的agent,有个功能是在代理启动的时候加载配置中心,拿到具体哪些目录下的类需要增强,这里碰到了类加载失败的问题。

二、类加载

1、问题

        这里使用了pom的设置,在class文件被拉进去,但是对象加载之前会执行premain(String agentArgs, Instrumentation instrumentation),这时候主要就是把拦截增强的类和方法设置好。

<Premain-Class>com.profiler.agent</Premain-Class>

        出现的问题是ClassNotFoundException,也就是编译时加载不到类,因为在agent里面指定了扫描路径和类类加载器 

JarFile jarFile = new JarFile(LogAgent.getBootstrapPath());instrumentation.appendToBootstrapClassLoaderSearch(jarFile);

2、双亲委派 

        根据《深入理解java虚拟机》,类加载通过双亲委派机制,避免类的重复加载,而BootstrapClassLoader是其中最顶级的类加载器,他只会加载放在《JAVA_HOME》\lib下面的文件,或者被-Xbootclasspath指定的路径,比如rt.jar、tools.jar。

        比如Object就是在rt.jar里面,保证在各种类加载器环境中他都是同一个类。这里就产生了一些问题:

        1、为什么不通过maven引入配置中心包

        2、agent引入的包是和main之后的maven引入的包产生互斥还是一样的,毕竟两个阶段的类加载器明显不一样

        3、为什么 Bootstrap ClassLoader 只加载 JVM 核心类库中的类,但是在maven里面引入之后他就可以加载了,难道是maven通过-Xbootclasspath将引入的包放到了对应的路径给Bootstrap加载吗

        带着问题去思考才会理解原理,这些我们在下一章解决方案里面再去分析

              

3、打破双亲委派

         既然提到了双亲委派,就要说说我们经典的打破模型了,尽管双亲委派模型是推荐的类加载机制,但在某些情况下,为了满足特定的需求,Java 中的一些类加载器实现选择了打破这一模型

        在《深入理解java虚拟机》中有叙说原因

        看看一些经典的框架 

  1. Tomcat 的 WebAppClassLoader: Tomcat 容器为每个部署的 web 应用程序提供了一个独立的类加载器(WebAppClassLoader)。为了允许每个 web 应用使用自己的类库版本,而不是容器级别或者系统级别的类库,Tomcat 的类加载器会首先尝试加载本地的类和资源,然后才委托给父类加载器。这样做可以避免类库版本冲突。

  2. OSGi(Open Service Gateway initiative)框架: OSGi 是一个用于模块化开发的 Java 框架,它允许应用程序由多个模块(称为 bundles)组成。OSGi 框架使用自定义的类加载器来加载每个 bundle,这些类加载器会根据 bundle 的依赖关系来加载类,而不是简单地遵循双亲委派模型。这允许不同的 bundle 使用不同版本的同一个库而不会发生冲突。

  3. JDBC 4.0 的 Service Provider Mechanism: 在 JDBC 4.0 中,引入了一种服务提供者机制,允许 JDBC 驱动程序通过在 JAR 文件的 META-INF/services 目录下提供一个服务配置文件来自动被发现和加载。这个机制使用了 Java 的 ServiceLoader 类,它不完全遵循双亲委派模型,因为它会查找所有可用的类加载器,包括当前线程的上下文类加载器。

  4. 热部署(Hot Deployment): 在一些应用服务器和开发工具中,为了支持热部署(即在不重启服务器的情况下替换或更新应用程序组件),可能会使用自定义的类加载器来加载和卸载类。这些类加载器通常会在加载类之前检查是否有更新的版本,从而打破了传统的双亲委派模型。

        这里有了一个新的问题,为了他们要这样做?以tomcat举例,在 Java Web 应用程序中,通常会有多个应用程序部署在同一个应用服务器(如 Tomcat)上。每个应用程序可能需要使用特定版本的第三方库(例如,日志库、JSON 处理库等)。如果所有应用程序都共享同一个类库版本,那么可能会出现以下问题:

  1. 版本冲突:不同的应用程序可能需要不同版本的同一个类库。如果应用服务器只提供了一个版本,那么可能会导致某些应用程序无法正常工作。

  2. 安全和隔离:如果所有应用程序共享相同的类库,那么一个应用程序中的问题可能会影响到其他应用程序。例如,一个应用程序中的安全漏洞可能会被其他应用程序利用。

  3. 升级和维护:当共享的类库需要升级时,可能需要同时升级所有依赖该类库的应用程序。这会增加维护的复杂性和风险。

        所以tomcat会按照以下顺序尝试加载类和资源:

  1. Web 应用程序的类库:首先尝试从部署在 /WEB-INF/lib 目录下的 JAR 文件和 /WEB-INF/classes 目录中加载类。

  2. 容器级别的类库:如果在 Web 应用程序的类库中找不到所需的类,类加载器会委托给容器级别的类加载器。

  3. 系统级别的类库:如果容器级别的类加载器也无法加载所需的类,最后会尝试使用系统级别的类加载器。

        这种调整看起来似乎是颠倒了双亲委派模型的顺序,但实际上,Tomcat 仍然保留了双亲委派的基本原则,即在尝试自己加载类之前,会先询问父类加载器。只不过在这种情况下,Tomcat 为了支持每个 Web 应用程序使用独立的类库,允许 WebAppClassLoader 有条件地优先加载特定的类路径。

        这种做法的目的是为了解决 Web 应用程序可能遇到的类库冲突问题,同时保持了双亲委派模型的好处,如避免类的重复加载和保证 Java 核心库的安全性

三、解决

        现在再来看一下怎么解决这个问题,方案有三种

1、自定义类加载器扫描配置中心所需要的类

        但是这有很大的隐患,因为配置中心所需要的包和加载模式可以说是非常繁琐多样的,自定义类加载器,看起来从底层上隔离了(因为类的重复加载是由类和类加载器确定唯一的),但是会导致许多问题,比如:

  1. 类型不兼容: 当两个相同全名的类由不同的类加载器加载时,尽管它们的名称相同,但它们在 JVM 中是不同的类型。这意味着,即使它们的字段和方法签名完全相同,它们也不兼容。例如,如果你尝试将一个类的实例赋值给另一个类加载器加载的同名类的引用,会抛出 ClassCastException

  2. 静态成员冲突: 如果两个相同全名的类由不同的类加载器加载,它们的静态成员(字段和方法)也是独立的。这可能会导致预期之外的行为,因为每个类版本都有自己的静态状态。

  3. 单例模式破坏: 如果你的应用程序依赖于单例模式,并且该单例类被不同的类加载器加载了多次,那么每个类加载器都会创建该类的一个实例,这违反了单例模式的原则。

  4. 服务提供者冲突: 在使用服务提供者接口(SPI)时,如果不同的类加载器加载了相同的服务提供者,可能会导致服务加载重复或不一致。

  5. 类加载器泄漏: 在复杂的类加载器层次结构中,如果不同的类加载器加载了相同的类,可能会导致类加载器无法被垃圾回收,从而导致内存泄漏。

  6. 资源管理问题: 如果不同的类加载器加载了相同的类,它们可能会尝试独立管理相同的资源(如文件句柄、数据库连接等),这可能导致资源冲突或泄漏。

        还有其他的不是类型问题

  1. 内存泄漏: 类加载器和它加载的类之间存在引用关系。如果自定义类加载器的实例被长期保持引用,那么它加载的所有类也无法被垃圾回收,这可能导致内存泄漏。

  2. 违反双亲委派模型: 如果自定义类加载器没有正确实现双亲委派模型,可能会导致一些问题,比如 Java 核心库的类被错误地重载,或者类的不一致性(比如同一个类被不同的类加载器加载)。

  3. 安全问题: 类加载器是 Java 安全模型的一部分。自定义类加载器如果没有正确地实现安全检查,可能会导致安全漏洞,比如加载未经验证的字节码。

  4. 性能问题: 不正确或者低效的类加载策略可能会导致性能问题。例如,频繁地创建类加载器实例和加载类可能会增加应用程序的启动时间和运行时的内存消耗。

  5. 兼容性问题: 自定义类加载器可能会与某些框架或库不兼容,因为这些框架或库可能依赖于特定的类加载机制。

        这里又引入了新的问题,为什么tomcat之类的就敢打破双亲委派,自定义类加载器?

        一方面这是他们投入了巨大的资源,做各种排障、迭代兼容,另外一方面人家牌子够大,如果跟他有冲突,其他的框架愿意花时间精力去适配他,所以正常情况下,这种方式我们玩不起,还是别想了

2、配置中心使用OpenApi替代SDK

        这其实是绕过类加载的问题,既然这个包加载不了,干脆不加载了,直接通过调用接口的方式去获取配置。

        这个其实无非就是配置中心的框架组把他的服务端数据开个接口出来,正常框架都支持的。

        但是这里还会引入新的问题,发送网络请求倒是没什么,jdk的原生包就支持了,只是写起来比较丑而已。但是问题在于返回数据的解析,返回的是一串复杂json,你要怎么解析,还是得引入包。

3、maven引入

        通过maven引入就要注意之前说的几个问题了

        1、为什么不通过maven引入配置中心包?

        这个看完下面的分析就知道了

        2、agent引入的包是和main之后的maven引入的包产生互斥还是一样的,毕竟两个阶段的类加载器明显不一样

        按原理说是不一样的,毕竟类重复是通过类加载器和类确定的,但是之前打破双亲委派也提到了

  1. 类型不兼容: 当两个相同全名的类由不同的类加载器加载时,尽管它们的名称相同,但它们在 JVM 中是不同的类型。这意味着,即使它们的字段和方法签名完全相同,它们也不兼容。例如,如果你尝试将一个类的实例赋值给另一个类加载器加载的同名类的引用,会抛出 ClassCastException

  2. 静态成员冲突: 如果两个相同全名的类由不同的类加载器加载,它们的静态成员(字段和方法)也是独立的。这可能会导致预期之外的行为,因为每个类版本都有自己的静态状态。

  3. 单例模式破坏: 如果你的应用程序依赖于单例模式,并且该单例类被不同的类加载器加载了多次,那么每个类加载器都会创建该类的一个实例,这违反了单例模式的原则。

  4. 服务提供者冲突: 在使用服务提供者接口(SPI)时,如果不同的类加载器加载了相同的服务提供者,可能会导致服务加载重复或不一致。

  5. 类加载器泄漏: 在复杂的类加载器层次结构中,如果不同的类加载器加载了相同的类,可能会导致类加载器无法被垃圾回收,从而导致内存泄漏。

  6. 资源管理问题: 如果不同的类加载器加载了相同的类,它们可能会尝试独立管理相同的资源(如文件句柄、数据库连接等),这可能导致资源冲突或泄漏。

        所以说引入可以,怎么解决呢,那就是把包打成完全不一样的,这样类的路径都不一样了。

        怎么实现呢?通过maven里面的relocation声明,他是通过转换路径名实现的,打成的包路径就变了(ps:在编码的时候看到的路径还是没有改变的,只在于运行打包替换)

        这样就可以解决以上的问题,但是有一个新的问题,配置中心太大了,配置中心的生态体系非常复杂,依赖的库非常多,所以说要relocation的非常多,对于加载启动也不友好

        所以relocation只适用于bytebuddy、Gson之类的没有其他依赖的库

<configuration><relocations><relocation><pattern>net.bytebuddy</pattern><shadedPattern>shaded.profiler.net.bytebuddy</shadedPattern></relocation></relocations></configuration>

        3、为什么 Bootstrap ClassLoader 只加载 JVM 核心类库中的类,但是在maven里面引入之后他就可以加载了,难道是maven通过-Xbootclasspath将引入的包放到了对应的路径给Bootstrap加载吗

        这里其实是第二个问题的延伸思考了,这时候的agent还是Bootstrap ClassLoader ,maven为什么可以加载?

        Maven 并不会通过 -Xbootclasspath 将引入的包放到对应的路径给 Bootstrap ClassLoader 加载。实际上,Maven 管理的依赖并不是由 Bootstrap ClassLoader 加载的,而是由系统类加载器(System ClassLoader)或者应用类加载器(Application ClassLoader)加载的。

        在 Maven 项目中添加依赖时,Maven 会处理这个依赖并将其放入项目构建的类路径(classpath)中,在运行时,这些依赖是由系统类加载器或应用类加载器加载的,而不是由 Bootstrap ClassLoader 加载的。

4、解决方案

        经过上面的分析,可以确定方案了,首先配置中心通过接口获取数据,其次在agent的maven里面引入Gson并且进行relocation。解决配置数据和json解析的问题。

四、总结

        以上分析除了作者的理解和资料研究,还有ld和同事cbc的讨论支持,有疑问可以沟通交流。

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

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

相关文章

HTML5(1)

目录 一.HTML5(超文本&#xff08;链接&#xff09;标记&#xff08;标签<>&#xff09;语言) 1.开发环境&#xff08;写代码&#xff0c;看效果&#xff09; 2.vscode 使用 3.谷歌浏览器使用 4.标签语法 5.HTML基本骨架&#xff08;网页模板&#xff09; 6.标签的…

Jetson Orin NX L4T35.5.0平台LT6911芯片 调试记录(2)vi discarding frame问题调试

基于上篇调试记录 Jetson Orin NX L4T35.5.0平台LT6911芯片 调试记录(1)MIPI问题调试-CSDN博客 1.前言 当通过gstreamer持续捕获视频设备时,帧数会下降,并且I输入越高,丢失的帧数越多。 当达到4k30hz时,它完全无法使用,系统会在几秒钟的收集后崩溃并重新启动 4k30hz …

【C++】:类和对象(下)

目录 一&#xff0c;再谈构造函数1.初始化列表2. 隐式类型转换的过程及其优化3. 隐式类型转换的使用4. explcit关键字5. 单参数和多参数构造函数的隐式类型转换 二&#xff0c;static成员1.静态成员变量2.静态成员函数 三&#xff0c;友元3.1 友元函数3.2 友元类 四&#xff0c…

Ansys Speos|进行智能手机镜头杂散光分析

本例的目的是研究智能手机Camera系统的杂散光。杂散光是指光向相机传感器不需要的散光光或镜面光&#xff0c;是在光学设计中无意产生的&#xff0c;会降低相机系统的光学性能。 在本例中&#xff0c;光学透镜系统使用Ansys Zemax OpticStudio (ZOS)进行设计&#xff0c;并使用…

LangChain入门2 RAG详解

RAG概述 一个典型的RAG应用程序,它有两个主要组件&#xff1a; 索引&#xff1a;从源中获取数据并对其进行索引的管道。这通常在脱机情况下发生。检索和生成&#xff1a;在运行时接受用户查询&#xff0c;并从索引中检索相关数据&#xff0c;然后将其传递给模型。 从原始数据…

机器学习:深入解析SVM的核心概念【一、间隔与支持向量】

直接阅读原始论文可能有点难和复杂&#xff0c;所以导师直接推荐我阅读周志华的《西瓜书》&#xff01;&#xff01;然后仔细阅读其中的第六章&#xff1a;支持向量机 间隔与支持向量 **问题一&#xff1a;什么叫法向量&#xff1f;为什么是叫法向量**什么是法向量&#xff1f;…

ChatGPT向付费用户推“记忆”功能,可记住用户喜好 | 最新快讯

4月30日消息&#xff0c;人工智能巨头OpenAI宣布&#xff0c;其开发的聊天机器人ChatGPT将在除欧洲和韩国以外的市场全面上线“记忆”功能。这使得聊天机器人能够“记住”ChatGPT Plus付费订阅用户的详细信息&#xff0c;从而提供更个性化的服务。 OpenAI早在今年2月就已经宣布…

AJAX家政系统 自营+多商家(高级授权)+独立端口 -源码下载

应用介绍 后台&#xff1a;https://service.hnajax.com/hxeJVakAdf.php/index/login AJAX家政系统 自营多商家(高级授权)独立端口 基于FastAdmin和原生微信小程序开发的一款同城预约、上门服务、到店核销家政系统&#xff0c;用户端、服务端(高级授权)、门店端(高级授权)各端…

HTML:认识HTML及基本语法

目录 1. HTML介绍 2. 关于软件选择和安装 3. HTML的基本语法 1. HTML介绍 HyperText Markup Language 简称HTML&#xff0c;意为&#xff1a;超文本标记语言 超文本&#xff1a;是指页面内可以包含的图片&#xff0c;链接&#xff0c;声音&#xff0c;视频等内容 标记&am…

76、堆-数据流的中位数

思路&#xff1a; 这个问题是动态数据流中位数查找问题。在数据流中&#xff0c;数据是逐个到来的&#xff0c;而我们需要在任何时候快速返回已有数据的中位数。中位数是将数据集分成两个等长的子集&#xff0c;一个包含所有较小的元素而另一个包含所有较大的元素。 为了高效解…

升级 Vite 5 出现警告 The CJS build of Vite‘s Node API is deprecated

错误描述 vue3-element-admin 项目将Vite4 升级至 Vite5 后,项目运行出现如下警告: The CJS build of Vites Node API is deprecated. See https://vitejs.dev/guide/troubleshooting.html#vite-cjs-node-api-deprecated for more details.图片 问题原因 Vite 官方弃用 C…

WIN10 anaconda 安装 CondaError: Run ‘conda init‘ before ‘conda activate‘

1 下载 https://www.anaconda.com/download/success 2 安装 3 修改环境变量 安装后修改环境变量 4 winrun 进入命令窗口 输入cmd 输入 conda info 5 创建 虚拟环境 conda create -n yolov8 python3.8 -y 6 CondaError: Run ‘conda init’ before ‘conda activate’ c…

在Primavera P6 中维护自定义活动栏

前言 自从 Henry Gantt 在 1910 年左右提出这个想法以来&#xff0c;以图形方式显示项目进度表并沿时间刻度显示条形图一直延续到当今最复杂和流行的项目进度系统中。在本文中&#xff0c;我们将仔细研究 Primavera P6 Professional 中的甘特图&#xff0c;并探索一些自定义其…

【新知实验室 - TRTC 实践】音视频互动 Demo、即时通信 IM 服务搭建

一、TRTC 初识 TRTC 是什么 TRTC&#xff08;Tencent RTC&#xff09;腾讯实时音视频&#xff0c;源自于 QQ 音视频团队&#xff0c;是基于 QQ 音视频多年来的音视频技术积累&#xff0c;位于腾讯云的 RTC 云服务。TRTC 支持腾讯会议、企业微信直播、微信视频号、腾讯云课堂、…

一个类实现Mybatis的SQL热更新

引言 平时用SpringBootMybatis开发项目&#xff0c;如果项目比较大启动时间很长的话&#xff0c;每次修改Mybatis在Xml中的SQL就需要重启一次。假设项目重启一次需要5分钟&#xff0c;那修改10次SQL就过去了一个小时&#xff0c;成本有点太高了。关键是每次修改完代码之后再重…

FineBI学习:K线图

效果图 底表结构&#xff1a;日期、股票代码、股票名称、开盘价、收盘价、最高价、最低价 步骤&#xff1a; 横轴&#xff1a;日期 纵轴&#xff1a;开盘价、最低价 选择【自定义图表】&#xff0c;或【瀑布图】 新建字段&#xff1a;价差&#xff08;收盘-开盘&#xf…

POETIZE个人博客系统源码 | 最美博客

源码介绍 POETIZE个人博客系统源码 | 最美博客 这是一个 SpringBoot Vue2 Vue3 的产物&#xff0c;支持移动端自适应&#xff0c;配有完备的前台和后台管理功能。 网站分两个模块&#xff1a; 博客系统&#xff1a;具有文章&#xff0c;表白墙&#xff0c;图片墙&#xf…

CSS 伪类、伪元素的应用实例:电池充电、高能进度条

一、目的 本文通过 CSS 伪类、伪元素&#xff0c;结合动画 animation 和 Vue 动态样式属性&#xff08;通过 CSS 变量&#xff09;的写法&#xff0c;来实现电池充电、高能进度条的效果&#xff0c;如下图所示。 二、基础知识 1、CSS 伪类、伪元素 简单概括成以下 4 点&#x…

谷粒商城实战(020 RabbitMQ-消息确认)

Java项目《谷粒商城》架构师级Java项目实战&#xff0c;对标阿里P6-P7&#xff0c;全网最强 总时长 104:45:00 共408P 此文章包含第258p-第p261的内容 消息确认 生产者 publishers 消费者 consumers 设置配置类 调用api 控制台 抵达brocker 代理 新版本ReturnCallbac…

DevEco Studio mac版启动不了【鸿蒙开发Bug已解决】

文章目录 项目场景:问题描述原因分析:解决方案:此Bug解决方案总结Bug解决方案寄语项目场景: 最近也是遇到了这个问题,看到网上也有人在询问这个问题,本文总结了自己和其他人的解决经验,解决了【DevEco Studio mac版启动不了】的问题。 问题描述 报错如下。 -------…