如何分析堆快照中的“保留大小”快速定位最耗费内存的代码对象
如何分析堆快照中的“保留大小”快速定位最耗费内存的代码对象

免费影视、动漫、音乐、游戏、小说资源长期稳定更新! 👉 点此立即查看 👈
什么是保留大小(Retained Size)?
说到内存分析,很多人第一反应是看对象自己有多大。但这里有个更关键的概念:保留大小。它衡量的不是对象自身占了多少字节,而是回答一个更实际的问题——如果把这个对象从内存里“连根拔起”,能顺带释放出多少空间?
举个例子,一个 HashMap 实例的 retained size 如果特别大,那事情就很有意思了。这意味着,不仅仅是这个 HashMap 对象本身,它里面装的所有键值对、支撑这些键值对的内部数组,甚至这些值对象下游引用的整个对象网络(比如一堆 String 或 ArrayList),全都因为它的存在而“活”着,GC 动不了它们。这才是真正卡住内存脖子的那个“关键先生”。
相比之下,浅层大小(shallow size)只算对象头和字段指针,太表面,容易误判;深层大小(deep size)倒是把引用链上的都算进来了,但它有个问题:会把那些被其他路径强引用的对象也算进去,干扰我们定位真正的“罪魁祸首”。
MAT 中按 Retained Size 排序时必须关掉“Keep unreachable objects”
打开 MAT 分析堆快照,直接按 Retained Heap 排序,你以为排在前面的就是“元凶”?先别急,这里有个常见的坑。
默认情况下,MAT 会勾选“Keep unreachable objects”选项。这个选项会让分析结果里混入大量已经不可达、本该被 GC 回收但还没来得及清理的对象。比如,某个大对象刚刚被程序置为 null,但 GC 还没跑,它就会出现在列表里。这类对象的 retained size 往往是 0 或者极小,但它们会挤占列表顶部的位置,把真正有问题的活对象给“淹没”了。
所以,正确的操作姿势是:先进入 Preferences → Memory Analyzer,找到 Keep unreachable objects 并取消勾选。做完这一步,再重新打开堆快照,点击 Retained Heap 列头进行排序。这时候,排在前 10 名的对象,才值得我们花时间深究。
看“支配树(Dominator Tree)”比看“直方图(Histogram)”更准
排查内存问题,很多人习惯先看直方图。但说实话,直方图主要按类名统计实例数和总 shallow size,对于定位泄漏点,帮助其实很有限。
真正的高手,会直接打开“支配树”。为什么?因为它强制体现了对象间“谁真正 hold 住谁”的唯一支配关系。在支配树里,每个节点的 retained size 就是它自己,加上所有被它唯一支配(即除了通过它,没有其他路径可达)的对象的总和。这直接对应了“删除它能释放多少内存”的核心问题。
操作路径很简单:Open Query Browser → Ja va → Dominator Tree。打开后,重点关注以下几类节点:
- 排在前列的,是非 JDK 的内部业务类,比如
com.example.service.CacheManager。 - 集合类(像
ArrayList、ConcurrentHashMap)的retained size如果远大于其自身的shallow size,说明它肚子里“装”了很多东西。 - 有时候,泄漏不是单个实例造成的,而是多个同类的实例“分头作案”。如果某个类的多个实例分散在不同路径,但它们的
retained size合计超过了堆的 15%,那也值得高度警惕。
点开对象后重点看 “Path to GC Roots” 里的“with all references”
找到 retained size 大的对象只是第一步。接下来要问:它为什么能活着?是谁在“保”它?
这时候,右键点击这个可疑对象,选择 Path to GC Roots → with all references。这条路径会清晰地告诉你,是什么引用链让它依然坚挺地留在堆里。
路径里透露的信息往往是破案的关键:
- 如果出现了
ja va.lang.Thread.localMap,那基本可以断定是 ThreadLocal 使用不当导致的内存泄漏。 - 如果路径指向了一个
static字段(比如MyClass.CACHE),那问题很可能出在静态缓存没有设置合理的淘汰机制上。 - 如果看到了
org.apache.catalina.loader.WebappClassLoader这类类加载器,那几乎可以锁定是 Web 应用热部署或卸载时,ClassLoader 泄漏的经典场景。
当然,也要注意过滤噪音。查看时可以排除 WeakReference 和 SoftReference 这类引用路径——它们本身不阻止 GC,通常不是优先处理的目标。
话说回来,最难的部分往往不是找到那个“大块头”,而是判断它该不该这么大。同一个 ConcurrentHashMap 实例,如果它是业务核心缓存,并且设计了完善的 LRU 和过期策略,那么 retained size 大是合理的,是功能需要。但如果发现它的 key 是不断新创建的 StringBuilder,value 里还挂着一堆未关闭的 InputStream,那它无疑就是内存泄漏的源头。所以,最终一定要结合代码的业务上下文来做判断,不能光盯着数字下结论。
游乐网为非赢利性网站,所展示的游戏/软件/文章内容均来自于互联网或第三方用户上传分享,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系youleyoucom@outlook.com。
同类文章
Less如何提升CSS维护性_使用参数化Mixin实现灵活组件
Less参数化Mixin:如何写出既灵活又可控的样式代码? Less参数化Mixin怎么写才不重复造轮子 开门见山,参数化Mixin的核心目标不是炫技,而是解决一个实际问题:把那些“可能会变”的样式值抽离出来。这样一来,样式规则只需定义一次,修改时就能全局生效,维护效率自然就上去了。关键在于,你得准
Vue 中的 Patch 过程是怎么工作的?从 VNode 到真实 DOM 的转化全指南
Vue 中的 Patch 过程是怎么工作的?从 VNode 到真实 DOM 的转化全指南 Patch 的核心目标:高效更新 DOM 简单来说,Vue 的 Patch 过程干的就是一件“聪明事”:它拿着新旧两份虚拟节点(VNode)清单,只去更新真实 DOM 里真正变了的那部分,而不是不管三七二十一,
CSS如何实现移动端加载占位骨架屏_利用CSS渐变色与动画效果
CSS如何实现移动端加载占位骨架屏:利用渐变色与动画效果 先明确一个核心概念:一个真正好用的骨架屏,本质上不是图片,而是用CSS背景渐变“画”出来的容器轮廓。关键在于,如何让background-image精准覆盖真实内容区域,同时巧妙地利用透明间隙来模拟文字或头像的留白。这听起来简单,但实际操作时
CSS如何实现侧边栏推拽切换_利用CSS动画平滑过渡布局
侧边栏推拽用 transform: translateX() 更流畅,避免 left margin-left 触发重排;初始隐藏用 translateX(-100%),配合 ease-out 或自定义 cubic-bezier 过渡更自然;移动端需谨慎 preventDefault() 并启用 -w
Ionic 7 中在 Tab 内实现页面内导航的完整教程
Ionic 7 中在 Tab 内实现页面内导航的完整教程 本文详解如何在 Ionic 7(Vanilla JS)中为单个 Tab 配置独立的嵌套路由系统,解决 ion-router 在 ion-tab 内无法正常跳转的问题,并提供可运行的结构化实现方案。 如果你正在用 Ionic 7 的纯 Ja v
- 日榜
- 周榜
- 月榜
1
2
3
4
5
6
7
8
9
10
1
2
3
4
5
6
7
8
9
10
相关攻略
2015-03-10 11:25
2015-03-10 11:05
2021-08-04 13:30
2015-03-10 11:22
2015-03-10 12:39
2022-05-16 18:57
2025-05-23 13:43
2025-05-23 14:01
热门教程
- 游戏攻略
- 安卓教程
- 苹果教程
- 电脑教程
热门话题

