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

【JVM深度解析】第15篇:JVM配置优化案例二:内存泄漏定位与修复(MAT分析全流程)

摘要内存泄漏是 Java 应用最隐蔽的性能杀手——它不会让你的应用立刻崩溃但会让它慢慢死去堆内存持续增长GC 越来越频繁最终 OOM。某物流追踪系统的内存从 2GB 增长到 8GB 只用了 48 小时每次 Full GC 后老年代不降反升。本案例完整记录使用Eclipse MATMemory Analyzer Tool进行堆转储分析的全流程从 dump 触发、到支配树分析、再到可疑泄漏点定位最终发现是一个静态 HashMap 缓存未清理的经典问题。修复后内存稳定在 3GBFull GC 后老年代大幅下降。一、问题背景1.1 业务场景某物流公司的实时追踪系统使用 JDK 11 Spring Boot 2.7部署在 16GB 内存的服务器上使用 CMS GC。系统负责实时接收 GPS 设备上报的位置数据延迟要求 5 秒。1.2 故障现象监控数据48 小时 Day 1, 00:00 - 堆使用2.1GB/16GBOld Gen: 1.2GB/8GB Day 1, 12:00 - 堆使用4.5GB/16GBOld Gen: 3.8GB/8GB ← 48小时内翻倍 Day 2, 00:00 - 堆使用7.8GB/16GBOld Gen: 7.2GB/8GB ← 接近上限 Day 2, 06:00 - Full GC OOM GC 日志特征 - Minor GC 正常每 30 秒一次 - Full GC 后老年代内存不降反升危险信号 - 老年代从 Full GC 前的 7GB 降到 6.8GB只回收了 200MB1.3 内存增长曲线内存泄漏的典型曲线 ┌──────────────────────────────────────────────────────────────────┐ │ │ │ 内存(GB) │ │ ↑ │ │ 8 │ _______________ │ │ 7 │ ____- │ │ 6 │ ___- │ │ 5 │ ___- │ │ 4 │ ____- │ │ 5 │ _____ │ │ 4 │ ____- │ │ 3 │ ____- │ │ 2 │ __- │ │ 1 │ │ │ └──────────────────────────────────────────────────────→ │ │ Day1 Day1 Day1 Day2 Day2 Day2 Day2 │ │ 00:00 06:00 12:00 00:00 06:00 12:00 18:00 │ │ │ │ 关键特征持续上升不随 GC 回落 │ │ 正常 GC 曲线锯齿状峰值不会持续升高 │ │ │ └──────────────────────────────────────────────────────────────────┘二、问题分析2.1 初步诊断# 确认内存泄漏特征$ jstat-gcutil123455000# 多次采样间隔 5 秒观察 Old Gen 是否持续上升S0 S1 E O M YGC YGCT FGC FGCT0.008.5045.0072.5085.2015612.342345.6758.010.0012.3050.0078.3085.2015712.452345.6758.12← O 持续上升0.0015.8055.0084.2085.2015812.562345.6758.23← O 持续上升0.0018.2060.0089.8085.2015912.672345.6758.34← 危险# 分析# - Old Gen 使用率从 72.5% 持续上升到 89.8%短短 15 秒# - Full GC 次数不变23次说明还没触发 Full GC# - 内存持续增长 确认内存泄漏2.2 堆转储准备# 方案一手动触发堆转储在 OOM 之前手动做$ jcmd12345GC.heap_dump /tmp/heap_leak_$(date%Y%m%d_%H%M%S).hprof# 方案二设置自动触发配置 JVM 参数# 在启动脚本中添加# -XX:HeapDumpOnOutOfMemoryError# -XX:HeapDumpPath/var/log/heapdump.hprof# -XX:HeapDumpBeforeFullGC ← Full GC 前也 dump# 等待 dump 完成约 2-5 分钟取决于堆大小ls-lh/tmp/heap_leak_*.hprof2.3 MAT 分析流程下载 Eclipse MAThttps://www.eclipse.org/mat/downloads.php# 启动 MATGUI 模式./MemoryAnalyzer-XX:UseG1GC/tmp/heap_leak_20260315_030000.hprof# 或使用命令行生成报告./ParseHeapDump.sh /tmp/heap_leak.hprof\org.eclipse.mat.api:suspects\org.eclipse.mat.api:top_components\org.eclipse.mat.api:overview三、MAT 分析实战3.1 Overview 概览打开堆转储文件后首先看 OverviewLeak Suspects 饼图分析 ┌──────────────────────────────────────────────────────────────────┐ │ Histogram 排名前10 │ ├──────────────────────────────────────────────────────────────────┤ │ Class Name │ Objects │ Shallow Heap │ Retain Heap│ │ ----------------------------------------------------------------│ │ char[] │ 2,345,678 │ 987,654,321 │ 2,147,XXX │ │ java.lang.String │ 1,234,567 │ 98,765,XXX │ 1,023,XXX │ │ java.util.HashMap$Node │ 890,123 │ 42,XXX,XXX │ 890,XXX │ │ java.util.HashMap │ 56,789 │ 2,XXX,XXX │ 780,XXX │ ← 可疑 │ com.example.GpsLocation │ 345,678 │ 55,XXX,XXX │ 456,XXX │ ← 大量对象 │ com.example.CacheEntry │ 89,012 │ 14,XXX,XXX │ 123,XXX │ └──────────────────────────────────────────────────────────────────┘ # 发现可疑点 # 1. HashMap 和 HashMap.Node 数量异常多 # 2. GpsLocation 对象有 34.5 万个还在堆里正常应该被回收3.2 Dominator Tree支配树支配树是 MAT 最强大的功能——它找出哪些对象支配了大量内存Dominator Tree 分析按 Retained Heap 排序 ┌──────────────────────────────────────────────────────────────────┐ │ Path To GC Roots: com.example.LocationCache.cache │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ com.example.LocationCache.cache ←──────┐ │ │ └─ HashMap (89,012 entries) │ │ │ └─ HashMap.Node[89,012] │ Retained: 780MB │ │ └─ CacheEntry (GpsLocation) │ │ │ │ │ 问题LocationCache 是一个 static 缓存不断添加数据但从未清理 │ │ │ └──────────────────────────────────────────────────────────────────┘3.3 Unreachable Objects 排除Histogram → Group by Class Loader → 查看是否有 ClassLoader 泄漏 通常需要排除 - 已卸载的类加载器的对象 - 虚引用/弱引用持有的对象正常会被回收 - 字符串常量池中的字符串3.4 定位泄漏根因// MAT 中Go To功能查看 LocationCache 类实例// 发现类定义publicclassLocationCache{// 泄漏根因静态 HashMap容量无上限privatestaticfinalMapString,GpsLocationcachenewHashMap();publicvoidupdate(StringdeviceId,GpsLocationlocation){cache.put(deviceId,location);// 只进不出}// 缺少清理方法// public void clearOldEntries() { ... } ← 不存在}四、问题代码分析4.1 原始问题代码ComponentpublicclassLocationCache{// 问题 1静态缓存生命周期 JVM 生命周期privatestaticfinalMapString,GpsLocationcachenewHashMap();// 问题 2只往里加不清理publicvoidupdateLocation(StringdeviceId,GpsLocationlocation){cache.put(deviceId,location);}// 问题 3没有 TTL没有大小限制publicGpsLocationgetLocation(StringdeviceId){returncache.get(deviceId);}}4.2 使用方代码ServicepublicclassGpsTrackingService{AutowiredprivateLocationCachecache;// 模拟调用每秒处理 1000 个 GPS 设备数据publicvoidprocessGpsData(ListGpsDatadataList){for(GpsDatadata:dataList){GpsLocationlocationnewGpsLocation(data);// 每条数据都往缓存里塞cache.updateLocation(data.getDeviceId(),location);}}}// 问题每天 86400 秒 * 1000 设备 8600 万条数据// HashMap 不断膨胀 → 内存泄漏五、解决方案5.1 方案一使用带 TTL 的缓存框架// 推荐使用 Caffeine高性能缓存库ComponentpublicclassLocationCache{// Caffeine支持 TTL、最大容量、淘汰策略privatefinalCacheString,GpsLocationcacheCaffeine.newBuilder().maximumSize(100_000)// 最大 10 万条.expireAfterWrite(Duration.ofMinutes(30))// 30 分钟 TTL.recordStats()// 记录统计.build();publicvoidupdateLocation(StringdeviceId,GpsLocationlocation){cache.put(deviceId,location);}publicOptionalGpsLocationgetLocation(StringdeviceId){returnOptional.ofNullable(cache.getIfPresent(deviceId));}// 暴露统计信息publicCacheStatsgetStats(){returncache.stats();}}5.2 方案二使用 WeakHashMap适合短生命周期缓存// WeakHashMap当 key 没有其他引用时可被 GC 回收ComponentpublicclassLocationCacheV2{// 适用场景缓存项的生命周期与业务对象关联privatefinalMapDevice,GpsLocationcachenewWeakHashMap();publicvoidupdateLocation(Devicedevice,GpsLocationlocation){cache.put(device,location);}}5.3 完整依赖!-- Maven 依赖 --dependencygroupIdcom.github.ben-manes.caffeine/groupIdartifactIdcaffeine/artifactIdversion3.1.8/version/dependency六、效果验证6.1 修复前后对比修复后 72 小时监控 Day 1, 00:00 - 堆使用2.1GB/16GBOld Gen: 1.1GB/8GB Day 1, 12:00 - 堆使用2.8GB/16GBOld Gen: 1.8GB/8GB ← 正常波动 Day 2, 00:00 - 堆使用2.5GB/16GBOld Gen: 1.5GB/8GB ← GC 后回落 Day 2, 12:00 - 堆使用2.9GB/16GBOld Gen: 1.9GB/8GB ← 稳定 GC 日志特征修复后 - Full GC 后老年代从 7GB 降到 1.8GB回收 5.2GB← 正常 - 内存使用呈现健康的锯齿状不再持续上升6.2 Caffeine 统计监控// 定期输出缓存统计Scheduled(fixedRate60000)publicvoidlogCacheStats(){CacheStatsstatscache.stats();log.info(Cache stats: hitRate{}, evictions{}, size{},stats.hitRate(),stats.evictionCount(),cache.estimatedSize());}// 日志输出示例// Cache stats: hitRate0.85, evictions1234, size67890// - 85% 命中率良好// - 每分钟约 1234 次淘汰正常 TTL 淘汰// - 缓存大小稳定在 6-7 万bounded七、经验总结7.1 内存泄漏常见模式┌──────────────────────────────────────────────────────────────────┐ │ Java 内存泄漏 Top 5 │ ├──────────────────────────────────────────────────────────────────┤ │ │ │ 1. 静态集合持有对象最常见 │ │ 解决使用带 TTL 或大小限制的缓存 │ │ │ │ 2. 监听器/回调未注销 │ │ 解决Spring PreDestroy 清理或使用 WeakReference │ │ │ │ 3. ThreadLocal 未清理 │ │ 解决在线程池中使用 ThreadLocal务必在 finally 中 remove() │ │ │ │ 4. 内部类持有外部引用 │ │ 解决使用静态内部类或使用 WeakReference │ │ │ │ 5. 数据库连接/流未关闭 │ │ 解决try-with-resources或使用连接池管理 │ │ │ └──────────────────────────────────────────────────────────────────┘7.2 MAT 使用技巧MAT 快捷键和技巧 1. CtrlShiftM → 按包名分组 2. CtrlF → 搜索对象 3. Right Click → Path To GC Roots → 查看根因 4. Java Basics → Thread Details → 查看线程持有 5. OQL → SQL 风格查询对象SELECT * FROM java.util.HashMap WHERE size() 1000系列导航上一篇【JVM深度解析】第14篇JVM配置优化案例一Full GC频繁导致服务不可用下一篇【JVM深度解析】第16篇JVM配置优化案例三CPU 100%排查线程死循环系列目录JVM深度解析系列全集参考资料Eclipse MAT DocumentationCaffeine Cache GitHubJava Memory Leaks: Tools, Causes, and PatternsDiagnosing Memory Leaks with MAT

相关文章:

【JVM深度解析】第15篇:JVM配置优化案例二:内存泄漏定位与修复(MAT分析全流程)

摘要 内存泄漏是 Java 应用最隐蔽的性能杀手——它不会让你的应用立刻崩溃,但会让它"慢慢死去":堆内存持续增长,GC 越来越频繁,最终 OOM。某物流追踪系统的内存从 2GB 增长到 8GB 只用了 48 小时;每次 Full…...

fay的funasr的使用

课程ID:fay_funasr作者:课程作者日期:2026-04-15T15:28版本:1.0.0章节数:7目录前置条件安装独立虚拟环境激活虚拟环境安装依赖启动funasrfay配置funasr测试效果第1节 前置条件开始之前,我们确保系统上已经安…...

DeerFlow 系列教程 第八篇 | 中间件体系——Agent 的生命周期管理

DeerFlow 系列教程 第八篇 本篇教程继续模块三:核心概念深度解析,从源码层面全面剖析 DeerFlow 的中间件体系。我们将拆解 15 层核心中间件的职责与实现、执行流程的正序/反序规则、条件中间件的动态组装逻辑,以及如何开发自定义中间件扩展 Agent 的能力边界。 前置知识 在…...

【JVM深度解析】第14篇:JVM配置优化案例一:Full GC频繁导致服务不可用

摘要 凌晨三点,告警响起:“订单服务 Full GC 次数异常”。登录服务器一看,Full GC 每隔 3 分钟就触发一次,每次停顿 3 秒以上,用户下单开始超时。本案例从 GC 日志分析入手,定位出老年代持续增长的根本原因…...

【AI面试临阵磨枪】详细解释 LLM、Token、Context、Prompt、Tool、MCP、Agent、Agent Skill 这些名词

一、 知识储备1. LLM (Large Language Model) - 大语言模型本质: 基于 Transformer 架构,在海量文本上进行预训练的概率预测引擎。面试深挖: 重点在于 “预测下一个 Token” 的本质。它并不真正“理解”含义,而是根据统计概率生成…...

告别环境冲突!用Anaconda在远程服务器上为不同项目创建独立PyTorch环境(MobaXterm操作指南)

多项目并行开发者的终极武器:Anaconda环境隔离与MobaXterm高效管理指南 当你在同一台服务器上同时推进三个深度学习项目时——一个需要PyTorch 1.8进行图像分割,另一个依赖PyTorch 1.12进行自然语言处理,第三个则基于TensorFlow 2.6进行时间序…...

Qt多界面切换踩坑实录:QStackedWidget内存泄漏?QTabWidget动态增删页卡的正确姿势

Qt多界面切换实战:规避内存泄漏与动态管理的高级技巧 在开发复杂的Qt桌面应用程序时,多界面切换是几乎每个项目都会遇到的核心需求。无论是向导式配置界面、多标签编辑器还是模块化工作区,QStackedWidget和QTabWidget都是最常用的解决方案。但…...

360°全景拼接相机开发避坑指南:海思3403平台4目方案常见问题解析

360全景拼接相机开发避坑指南:海思3403平台4目方案实战解析 当四颗摄像头同时凝视世界时,工程师看到的往往是四幅难以调和的画面。海思3403平台作为全景拼接领域的主力芯片,其四目方案在车载监控、VR内容采集等场景展现独特优势,…...

手把手教你用Arduino和PulseSensor做个心率监测仪(附Processing上位机调试技巧)

从零打造Arduino心率监测仪:硬件搭建与数据处理全指南 在创客圈里,健康监测设备一直是热门DIY项目。相比市面上动辄上千元的专业医疗设备,用Arduino和PulseSensor自制心率监测仪不仅成本低廉(整套材料不到200元)&#…...

代码随想录 27(动态规划)

力扣 509.斐波那契数 思路 动态规划五部曲: 确定dp数组已经下标的含义确定递推公式数组初始化确定遍历顺序举例推导dp数组 根据题目和五步曲,分析如下: dp[i] 含义是:第 i 个斐波那契数是 dp[i]递推公式题目已经给出:…...

Java 8升级Java 17实战:用AWS Transform Custom自动化迁移Spring Boot项目完整教程

Java 8升级Java 17实战:用AWS Transform Custom自动化迁移Spring Boot项目完整教程 你手上有多少个还跑在 Java 8 上的项目?别装了,我知道答案——“不少”。Java 8 发布到现在都十年了,可企业里大把项目还钉在上面不敢动。不是不…...

从拼写纠错到智能推荐:手把手教你用Spring Boot整合字符串相似度算法(附完整项目)

从拼写纠错到智能推荐:手把手教你用Spring Boot整合字符串相似度算法(附完整项目) 在电商搜索框中输入"iphnoe"时自动提示"iphone",在内容平台浏览一篇文章后推荐相似主题——这些智能功能背后都离不开字符串…...

ngx_signal_handler

1 定义 ngx_signal_handler 函数 定义在 /nginx-1.24.0/src/os/unix/ngx_process.cstatic void ngx_signal_handler(int signo, siginfo_t *siginfo, void *ucontext) {char *action;ngx_int_t ignore;ngx_err_t err;ngx_signal_t *sig;ignore 0;…...

从CTF到运维:聊聊MySQL Handler这个‘偏门’但好用的命令

从CTF到运维:MySQL Handler命令的双面应用手册 第一次在CTF比赛中遇到MySQL Handler命令时,我正卡在一道Web题目上。题目要求绕过常规的SELECT查询限制获取管理员密码,正当我准备放弃时,Handler命令像一把瑞士军刀般解决了问题。后…...

保姆级拆解:NCCL路径计算如何影响你的多GPU训练性能(附排查脚本)

深度解析NCCL路径计算对多GPU训练性能的影响与优化实践 当你在8卡服务器上运行PyTorch DDP训练时,是否遇到过GPU3的利用率始终比其它卡低30%的情况?或者在使用DeepSpeed进行多节点训练时,发现跨节点通信耗时占据了整个迭代时间的40%以上&…...

Fix-Kindle-Ebook-Cover:一站式解决Kindle电子书封面损坏问题

Fix-Kindle-Ebook-Cover:一站式解决Kindle电子书封面损坏问题 【免费下载链接】Fix-Kindle-Ebook-Cover A tool to fix damaged cover of Kindle ebook. 项目地址: https://gitcode.com/gh_mirrors/fi/Fix-Kindle-Ebook-Cover 你是否曾经遇到过这样的困扰&am…...

你为了隐私从GPT搬去Claude,现在它让你交护照

今年二月,在美国发生的那一波汹涌的迁移,大概是AI公司历史上最富戏剧性的“用脚投票”。OpenAI和五角大楼签了合同,把模型部署到国防部的机密网络里;Anthropic则因为坚持不让自家模型用于这些领域,而被美国所有联邦机构…...

原神帧率解锁指南:如何让你的游戏体验飞起来?

原神帧率解锁指南:如何让你的游戏体验飞起来? 【免费下载链接】genshin-fps-unlock unlocks the 60 fps cap 项目地址: https://gitcode.com/gh_mirrors/ge/genshin-fps-unlock 想象一下,你刚刚入手了一台高刷新率的显示器&#xff0c…...

AutoWareAuto框架全解析:自动驾驶的融合感知、定位、决策规划与控制模块思维导图及代码注释

自动驾驶,AutoWareAuto框架全框架梳理思维导图及代码注释。 授人以鱼不如授人以渔,涵盖:融合感知模块,定位模块,决策规划模块,控制模块,预测模块等较为详细的注释(并非每行都有注释&…...

Claude Code用户反映使用配额消耗速度异常加快

Claude Code用户表示,他们的使用配额正在以比以往更快的速度耗尽——这一持续性问题已经得到Anthropic在Reddit和X平台上的官方确认。本周一,Anthropic在Reddit上回应了用户的质疑,写道:"我们已注意到用户在Claude Code中的使…...

RK3588/3568点MIPI屏避坑实录:从‘段错误’到完美显示的三个关键调试技巧

RK3588/3568 MIPI屏幕调试实战:从硬件排查到时序优化的全链路解决方案 当一块MIPI屏幕在RK3588或RK3568平台上无法正常点亮时,工程师往往需要从硬件链路到软件配置进行系统性排查。本文将分享三个关键阶段的调试技巧,帮助开发者快速定位问题根…...

医学图像配准利器Elastix:从零开始的实战配置与核心应用

1. 为什么选择Elastix进行医学图像配准 第一次接触医学图像配准的朋友可能会问:市面上这么多工具,为什么偏偏要选Elastix?这个问题问得好。我刚开始做医学影像分析时也纠结过,直到在实验室前辈的推荐下尝试了Elastix,才…...

小白程序员必看:收藏GraphRAG,轻松驾驭大模型专业问答难题!

大语言模型在专业领域应用受限,传统RAG存在理解复杂查询、整合分散知识、系统效率瓶颈等挑战。GraphRAG通过结合知识图谱与检索增强生成,将文本转换为结构化知识图谱,支持多跳推理,提升AI在专业领域的深度理解和回答能力。工作流程…...

用YOLOv8/v7/v6/v5搭建一个能识别条形码和二维码的Web应用(Streamlit实战教程)

从零构建基于YOLO的条码识别Web应用:Streamlit全流程指南 1. 环境准备与工具选择 在开始构建条码识别Web应用之前,我们需要明确技术选型和开发环境。本项目的核心是结合YOLO系列目标检测模型与Streamlit轻量级Web框架,实现一个即插即用的条码…...

别再死记公式了!用Python和PyTorch手把手复现扩散模型的采样过程(附完整代码)

用Python和PyTorch实战扩散模型采样:从噪声到图像的魔法之旅 想象一下,你手中有一张完全由随机噪声组成的图片,就像老式电视机失去信号时的雪花屏。通过一系列精心设计的数学变换,这些无序的噪点逐渐重组、凝聚,最终变…...

Pixel Language Portal 算法优化案例:卷积神经网络跨维特征提取

Pixel Language Portal 算法优化案例:卷积神经网络跨维特征提取 1. 效果亮点概览 在计算机视觉领域,传统卷积神经网络(CNN)已经展现出强大的特征提取能力。但当我们将Pixel Language Portal技术与CNN结合后,效果提升…...

Notepad++最新版更新|安全修复+VS Code对比,免费开源编辑器首选(附批量处理技巧)

摘要:Notepad近期接连更新,修复重大安全漏洞,本文详解最新版更新内容、安全避坑指南,对比VS Code核心差异,分享正则替换、宏录制等批量处理技巧,附官方正版下载渠道,帮程序员高效选型、安全用对…...

LeetCode 239. Sliding Window Maximum 题解

LeetCode 239. Sliding Window Maximum 题解 题目描述 给你一个整数数组 nums,有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动窗口内的 k 个数字。滑动窗口每次只向右移动一位。 返回滑动窗口中的最大值。 示例 1: 输入…...

放弃Keil自带的Pack Installer吧!手把手教你离线安装STM32G0芯片支持包(以STM32G0xx_DFP为例)

告别Keil在线安装困境:STM32G0芯片支持包离线安装全攻略 每次打开Keil的Pack Installer等待进度条缓慢爬升时,你是否也经历过那种焦灼?特别是在公司内网环境下,下载速度堪比蜗牛爬行,甚至频繁中断重试。作为嵌入式开发…...

别再乱用OneHot了!用Pandas的get_dummies处理分类变量,这3个参数能帮你省一半内存

别再乱用OneHot了!用Pandas的get_dummies处理分类变量的3个内存优化技巧 刚入行做数据分析时,我总喜欢无脑用OneHotEncoder处理所有分类变量——直到某次处理电商用户数据时,内存直接爆了。那次经历让我明白:分类变量编码不是简单…...