【工作中问题解决实践 十】一次内存泄露排查-MAT使用指南

news2024/12/24 8:36:08

最近体验了一把当医生的感觉,定位病根病因,感觉这种要揪出问题的感觉很爽,并不觉得麻烦,这里将整个排查过程记录一下,方便之后再遇到类似问题有应对之道。

问题背景

2023-07-18 早上还在睡梦中的俺被一条条报警消息铛铛铛的吵醒,这才发现我们的服务早上突然大量请求499,由于我们的服务相对基础,所以马上故障组拉群开始排查解决,先感觉分批次重启容器止损。这里get到的两个重点:

  • 监控一定要做,有了监控心里就有底了,且阈值设置的稍微低一些,这样可以赶在实际影响业务前得到通知
  • 遇到问题一定要及时摘流重启止损,问题分析现场留存先放放,当然如果监控阈值低的情况下,可以先摘流dump一下内存

我们当然是采取了及时止损的方式,当然也就丢失了现场,所以排查问题需要降低监控阈值再等一周发现后处理,不过这个没关系,首先不影响线上稳定才是第一要务!

实际原因

首先499的含义是客户端主动关闭连接,那是为什么呢?是QPS扛不住吗?并没有,实际上当天监控显示QPS才160多,远低于压测值。继而又观察容器本身的监控状态,这才发现容器内存已经飚到94%了,而且不止一台,所有容器都在94%左右,所以并非是单台容器的问题。于是我们把目光转向了JVM:
在这里插入图片描述
这才惊讶的发现,老年代一直不回收,堆内存匀速上升,一直仰赖每周两次的发版上线让其恢复起点,18日这个周二早上的上一个发版日发版时间过早,就这么点儿时间差,堆内存直接飚了上来。于是乎我们大概知道了问题原因:堆内存一定发生了泄露

内存泄露

Java内存泄露是指在Java应用程序中,由于未正确释放不再使用的内存,导致内存占用不断增加,最终可能耗尽可用内存资源,导致应用程序性能下降甚至崩溃。内存泄露是一种常见的程序缺陷,可能由多种原因引起,以下是一些可能导致内存泄露的原因:

  1. 对象引用未被释放:如果在使用完一个对象后未显式将其引用置为null,该对象可能无法被垃圾收集器回收,从而导致内存泄露。

  2. 静态引用:静态变量持有对象的引用,如果没有正确管理静态引用,即使对象不再使用,也无法被垃圾收集器回收。

  3. 集合类未正确管理:集合类(如List、Map、Set)可能会持有对象的引用,如果在集合中添加了对象但未在后续操作中正确移除,这些对象可能会一直保持在内存中。

  4. 资源未释放:如果程序使用了文件、网络连接、数据库连接等资源,但未正确关闭这些资源,可能会导致资源占用不释放,从而引发内存泄露。

  5. 监听器未移除:如果在应用中注册了监听器(例如GUI事件监听器、定时任务等),但忘记在不需要时移除这些监听器,可能导致对象无法被回收。

  6. 循环引用:对象之间相互引用,形成循环引用时,即使对象本身不再被使用,由于彼此之间的引用关系,也无法被垃圾收集器回收。

  7. 使用缓存:虽然缓存可以提高性能,但如果未正确管理缓存的生命周期和大小,可能导致缓存中的对象一直保持在内存中,引发内存泄露。

  8. 异常处理不当:异常可能导致程序流程中断,如果异常处理不当,可能导致资源未正确释放。

要避免内存泄露,开发人员应该仔细管理对象的生命周期,确保及时释放不再需要的资源和引用。使用强引用、软引用、弱引用等不同类型的引用,可以帮助更好地控制对象的回收时机。同时,定期进行代码审查和性能分析,以及使用内存分析工具来检测和解决潜在的内存泄露问题也是非常重要的。

我们常用的Spring框架也有一些常见内存泄露常见,在Spring框架中,由于其底层使用了Java语言,内存泄露问题也可能出现。以下是一些常见导致内存泄露的Spring框架相关行为:

  1. 未关闭资源: Spring管理了许多资源,如数据库连接、会话、事务等。如果在使用完这些资源后未正确关闭,可能导致资源未释放,最终引发内存泄露。

  2. Spring Bean 生命周期管理不当: 如果在配置Spring Bean时没有正确设置作用域或生命周期,可能导致Bean无法正确地被销毁,从而引发内存泄露。

  3. 内部类持有外部类的引用: Spring中的一些组件,如事件监听器、AOP代理等,可能会导致内部类持有外部类的引用,从而阻止外部类的垃圾回收,导致内存泄露。

  4. 使用缓存不当: Spring提供了缓存支持,但如果在使用缓存时没有正确管理缓存的生命周期和大小,可能导致缓存中的对象一直保持在内存中,引发内存泄露。

  5. 定时任务未正确管理: Spring的定时任务功能可能导致内存泄露,如果任务未正确取消或管理,可能导致对象无法被垃圾收集器回收。

  6. 循环依赖: Spring容器中的循环依赖问题可能导致Bean无法正确销毁,从而引发内存泄露。

  7. 使用ApplicationContext引起的泄露: 如果在应用程序中持有了ApplicationContext的引用,而没有正确释放,可能会导致整个ApplicationContext及其管理的Bean无法被回收。

为了避免在Spring应用程序中出现内存泄露,开发人员应该注意上述问题,并采取适当的措施来管理资源、Bean的生命周期,以及其他Spring相关的组件。进行定期的代码审查和性能分析,使用内存分析工具,可以帮助发现和解决潜在的内存泄露问题。

标红重点!因为我们这次内存泄露就与此相关:继续提问:使用ApplicationContext引起的泄露举个例子

  • 使用ApplicationContext引起的泄露是指在应用程序中持有了ApplicationContext的引用,但没有正确释放,从而导致整个ApplicationContext及其管理的Bean无法被垃圾回收,进而导致内存泄露。以下是一个例子来说明这种情况:假设你有一个Spring Boot应用程序,其中包含一个自定义的ApplicationContextHolder类,用于持有ApplicationContext的引用:
public class ApplicationContextHolder {
    private static ApplicationContext applicationContext;

    public static void setApplicationContext(ApplicationContext context) {
        applicationContext = context;
    }

    public static ApplicationContext getApplicationContext() {
        return applicationContext;
    }
}

在应用程序启动时,你将ApplicationContext设置到ApplicationContextHolder中:

@SpringBootApplication
public class MyApp {
    public static void main(String[] args) {
        SpringApplication.run(MyApp.class, args);
        ApplicationContext context = SpringApplication.run(MyApp.class, args);
        ApplicationContextHolder.setApplicationContext(context);
    }
}

在某个地方,你可能会通过ApplicationContextHolder来获取ApplicationContext,例如在一个服务类中:

public class MyService {
    public void doSomething() {
        ApplicationContext context = ApplicationContextHolder.getApplicationContext();
        // 使用ApplicationContext执行一些操作
        // ...
    }
}

这种做法可能导致内存泄漏,因为ApplicationContext被持有并可能无法被垃圾收集器回收。例如,如果MyService的实例一直存在,它持有的ApplicationContext引用也会一直存在,从而阻止ApplicationContext及其管理的Bean被释放

要避免这种情况,应该尽量避免在应用程序中持有ApplicationContext的引用。ApplicationContext通常由Spring框架管理,它会在合适的时候进行销毁和垃圾回收。如果确实需要在某些地方获取ApplicationContext,应该在使用完之后及时释放引用,避免长时间持有引用导致内存泄漏。通常情况下,可以通过依赖注入(DI)等方式来获取所需的Bean,而无需直接持有ApplicationContext的引用。

DUMP内存

dump的方式有很多,很多公司内部集成了自己的工具,但dump时首先要干的是摘流,确保你dump内存时没有请求进来,不要影响业务的正常使用。当然通用的在Java中,可以使用一些内存分析工具和指令来监测和分析应用程序的内存使用情况。以下是一些常见的Java内存分析指令和工具:

  1. jps (Java进程状态工具):用于列出当前系统中运行的Java进程,并显示它们的进程ID和主类名称。

    示例用法:jps -v

  2. jstat (Java统计信息监视工具):用于收集和显示Java虚拟机(JVM)运行时的各种统计信息,如堆内存使用情况、垃圾回收统计等。

    示例用法:jstat -gc <pid> <interval> <count>

  3. jmap (Java内存映像工具):用于生成堆转储快照,可以用于分析堆内存使用情况、对象分布等。

    示例用法:jmap -dump:live,format=b,file=<filename> <pid>

  4. jhat (Java堆分析工具):用于分析jmap生成的堆转储快照,可以通过浏览器查看对象信息和引用关系。

    示例用法:jhat <heap_dump_file>

  5. jstack (Java堆栈跟踪工具):用于生成Java线程的堆栈跟踪信息,帮助识别死锁、线程等待等问题。

    示例用法:jstack <pid>

  6. VisualVM (Visual Java Monitoring and Management Console):一个图形化工具,可以用于监视和分析应用程序的性能和内存使用情况,提供了多种功能,包括堆转储、线程分析等。

  7. MAT (Eclipse Memory Analyzer):一个强大的内存分析工具,用于分析堆转储快照,帮助识别内存泄漏和优化内存使用。

这些工具和指令可以帮助开发人员诊断应用程序的内存问题,包括内存泄漏、垃圾回收性能等方面的情况。根据具体的问题和需求,你可以选择合适的工具和指令来进行内存分析。

MAT分析

在这里下载最新版本的MAT :Eclipse Memory Analyzer
在这里插入图片描述
因为最新版的MAT仅支持JDK17及以上,所以在这里:JDK20下载下载最新版的JDK
在这里插入图片描述
还需要注意的是MAT需要进行配置,如果你电脑安装了多个JDK版本需要指定,并且如果你要分析的DUMP文件比较大,需要调大MAT的配置:这里我的配置参考如下:

-vm 
C:\Program Files\Java\jdk-20\bin\javaw.exe
-startup
plugins/org.eclipse.equinox.launcher_1.6.400.v20210924-0641.jar
--launcher.library
plugins/org.eclipse.equinox.launcher.win32.win32.x86_64_1.2.700.v20221108-1024
-vmargs
--add-exports=java.base/jdk.internal.org.objectweb.asm=ALL-UNNAMED
-Xmx8192m

内存分配了8个G,然后指定了JDK运行版本,这样就可以了。

寻根定位

好的,现在dump文件也有了,MAT工具也就位了【附送网上找的MAT最全介绍一文深度讲解JVM 内存分析工具 MAT及实践】,那就开始着手分析,MAT会自动给出探测意见:
在这里插入图片描述
同时会告诉你三个内存泄露可能导致的实际原因
在这里插入图片描述
甚至会贴心的告诉你问题之间可能存在关联:
在这里插入图片描述
最厉害的是点击问题进入,它甚至能给你直接打印出堆栈!告诉你哪里的代码发生泄露了
在这里插入图片描述

从堆栈入口很容易就定位问题了。问题代码不便于贴出来,实际原因就是这里没有使用Spring的容器管理,而是通过通过AutowireCapableBeanFactory容器外注入的方式注入bean的。而applicationContext被获取用来管理这些bean,且applicationContext被FormFactory引用着。而某个大佬又在applicationContext里疯狂实例对象,三天左右大概1100万个对象。这个不泄露都说不过去

总结一下

照例总结一下,线上出了问题不要慌,也别想着保留现场,先止损!平时的报警机制要建立好且阈值要低些,这样才能先于业务发现并解决问题。还有就是MAT是真香!

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

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

相关文章

CTF流量题解http1.pcapng

使用Wireshark工具打开流量文件http1.pcapng&#xff0c;如下图所示。 在过滤检索栏输入http&#xff0c;wireshark自动进行过滤。

【EI复现】基于阶梯碳交易的含P2G-CCS耦合和燃气掺氢的虚拟电厂优化调度(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

太水了,4年经验还不如一年的,难怪面试过不了

前言 看到朋友圈一个HR发的招聘信息&#xff0c;招聘2名外包测试开发岗位&#xff0c;如果只会pythonpytestrequest这种基础的测开技术&#xff0c;极大概率是过不了面试的。 一方面为她发的信息所惊讶&#xff0c;因为外包的岗位一般在我们看来薪资不高且要求较低&#xff1…

IPC之二:使用命名管道(FIFO)进行进程间通信的例子

IPC 是 Linux 编程中一个重要的概念&#xff0c;IPC 有多种方式&#xff0c;本文主要介绍命名管道(FIFO)&#xff0c;命名管道可以完成同一台计算机上的进程之间的通信&#xff0c;本文给出了多个具体的实例&#xff0c;每个实例均附有完整的源代码&#xff1b;本文所有实例在 …

axios接受文件流并下载

需求场景 前端发送请求&#xff0c;后端传回文件流&#xff0c;前端接受到后立刻打开下载窗口下载文件 注意事项 请求api需要添加&#xff1a;responseType:blob&#xff0c; axios拦截器拦截错误状态码 (假设是code) 那里的if从res.code ! 200改为res.code && res.…

SSD202D-kernel-uimage后面加入dtb

情况是这样的,由于我们这边烧录的是uImage.xz 是经过压缩的uimage文件,涉及到解码,boot获取dtb会需要解码,解码不知为何会延时十几秒等待 这是万万不能的,于是就使用了别的方法就把dtb放到kernel的空间多余的地方,这样只要能读到即可 于是我开始了设计方法 可以看到这个dtb的…

以mod_jk方式整合apache与tomcat(动静分离)

前言&#xff1a; 为什么要整合apache和tomcat apache对静态页面的处理能力强&#xff0c;而tomcat对静态页面的处理不如apache&#xff0c;整合后有以下好处 提升对静态文件的处理性能 利用 Web 服务器来做负载均衡以及容错 更完善地去升级应用程序 jk整合方式介绍&#…

Sqlserver还原数据库为另外的名字

Sqlserver还原数据库为另外的名字 在工作中需要还原数据库的时候原来的数据库不变&#xff0c;而是需要还原成一个新的数据库 1、备份test数据库 2、新建一个test1数据库 3、设置test1数据库文件的权限 右键这两个文件->属性 4、在test1上还原数据库 5、数据库还原配置…

比特鹏哥5-数组【自用笔记】

比特鹏哥5-数组【自用笔记】 1.数组的概念2.一维数组的创建和初始化创建的语句结构初始化的语句结构 3.一维数组的使用数组的下标&#xff1a;从0开始&#xff0c;n个数组&#xff0c;最后一个的下标是n-1 4.一维数组在内存中的存储5.sizeof计算数组元素个数可以计算元素个数并…

守住L2?争夺高阶智驾?留给外资Tier1的时间不多了!

14.08%&#xff0c;这是2023年1-6月中国市场&#xff08;不含进出口&#xff09;乘用车前装标配L2&#xff08;含L2&#xff09;搭载中国本土系统解决方案的份额占比。而在NOA等高阶赛道&#xff0c;中国本土势力已经占据上风。 这个过去一直被外资Tier1垄断的智能化细分市场&a…

机器学习深度学习——循环神经网络RNN

&#x1f468;‍&#x1f393;作者简介&#xff1a;一位即将上大四&#xff0c;正专攻机器学习的保研er &#x1f30c;上期文章&#xff1a;机器学习&&深度学习—语言模型和数据集 &#x1f4da;订阅专栏&#xff1a;机器学习&&深度学习 希望文章对你们有所帮助…

JDBC处理批量数据提高效率

文章目录 0 说明1 如何使用jdbc操作数据库1.1 加载数据库驱动1.2 建立数据库连接1.3 创建Statement或者PreparedStatement用来执行SQL1.4 开始执行SQL语句1.5 处理结果集1.6 关闭连接1.7 完整代码 2 批量操作数据库3 如何打印SQL语句4 jdbc常用开源类库5 获取自增id6 获取数据源…

【CSS】网格布局(简单布局、网格合并、网格嵌套)

文章目录 CSS网格布局&#xff08;Grid Layout&#xff09;1. 简单布局2. 网格合并3. 网格嵌套4. 总结 CSS网格布局&#xff08;Grid Layout&#xff09; CSS网格布局&#xff08;Grid Layout&#xff09;是一种强大且灵活的CSS布局系统&#xff0c;允许开发者以网格形式组织和…

快乐的马里奥(广搜入门)

题面 题目描述 马里奥是一个快乐的油漆工人&#xff0c;这天他接到了一个油漆任务&#xff0c;要求马里奥把一个 n 行 m 列的矩阵每一格都用油漆标记一个数字&#xff0c;标记的顺序按照广度优先搜索的方式进行&#xff0c;也就是他会按照如下方式标记&#xff1a; 1、首先标记…

基于springboot+vue的房屋租赁系统(前后端分离)

博主主页&#xff1a;猫头鹰源码 博主简介&#xff1a;Java领域优质创作者、CSDN博客专家、公司架构师、全网粉丝5万、专注Java技术领域和毕业设计项目实战 主要内容&#xff1a;毕业设计(Javaweb项目|小程序等)、简历模板、学习资料、面试题库、技术咨询 文末联系获取 项目介绍…

云原生之使用Docker部署homarr个人导航页

云原生之使用Docker部署homarr个人导航页 一、homarr介绍1.1 homarr简介1.2 homer特点 二、本地环境介绍2.1 本地环境规划2.2 本次实践介绍 三、本地环境检查3.1 检查Docker服务状态3.2 检查Docker版本3.3 检查docker compose 版本 四、下载homarr镜像五、部署homarr导航页5.1 …

彩虹云商城搭建完整教程 完整的学习资料

彩虹云商城搭建完整教程 完整的学习资料提供给大家学习 随着电子商务的快速发展&#xff0c;越来越多的企业开始意识到开设一个自己的电子商城对于销售和品牌推广的重要性。然而&#xff0c;选择一家合适的网站搭建平台和正确地构建一个商城网站并不是一件容易的事情。本文将为…

塔矢行洋对战藤原佐为,谁才是最接近神之一手的人

大家好, 我是嘉宾, 今天我们来盘点一下古今第一高手对局 &#xff0c;塔矢行洋对战藤原佐为&#xff0c;谁才是最接近神之一手的人&#xff0c; 在所有设定都点击好之后, 塔矢行洋下出了自己的第一步 添加图片注释&#xff0c;不超过 140 字&#xff08;可选&#xff09; 佐…

C语言内嵌汇编

反编译&#xff08;二进制文件或者so库&#xff09; objdump --help objdump -M intel -j .text -ld -C -S out > out.txt #显示源代码同时显示行号, 代码段反汇编-M intel 英特尔语法-M x86-64-C:将C符号名逆向解析-S 反汇编的同时&#xff0c;将反汇编代码和源代码交替显…

C++利用mutex和thread实现一个死锁

程序 #include<iostream> #include<mutex> #include<thread> using namespace std; mutex mtx1; mutex mtx2; void A(){mtx1.lock();cout<<"a:mtx1"<<endl;this_thread::sleep_for(chrono::milliseconds(1000));mtx2.lock();cout<…