鸿蒙小助手 2026-10-21 15:46:00 发布本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:
内存泄漏怎么查?JSLeak 教你用“新旧快照对比”揪出元凶-华为开发者话题 | 华为开发者联盟
写鸿蒙 ArkTS / JS 应用,最让人抓狂的 Bug 之一就是内存泄漏:界面越来越卡、内存越吃越多,最后直接闪退。你手里明明有一堆 .heapsnapshot、.rawheap 堆快照,报告也能跑出来,可就是看不懂——几万个对象里,到底哪个是泄漏、哪个只是正常变大、哪个该先查?
jsleak-analysis 这套方法要做的,就是把“堆快照里海量的对象”,变成一份“谁在泄漏、为什么泄漏、怎么修”的明白账。
一、一句话讲清思路
拿新旧两个版本的快照放到一起比一比:谁是新冒出来的?谁悄悄变大了?谁的引用链越来越危险?——谁的嫌疑就最大,谁就该优先修。
二、别被“一次很大”骗了,要看趋势
一张快照就像一张“体检照片”,只能告诉你拍照那一刻谁最胖,却分不清它是天生就胖(正常缓存)、还是一路吃胖(持续泄漏)、还是突然水肿(偶发波动)。
真正的泄漏,通常是“持续出现、持续变大”的。所以这套方法分两步:先把同一版本的多个快照合并起来看趋势,再跨版本比较谁变大了。
三、把“碎片对象”打包成“问题簇”
每个快照会被拆成一张对象图:谁指向谁、每个对象占多大内存、离“垃圾回收根”(GC Root)有多远。
这里有个关键动作:只算“强引用链”。JS / ArkTS 里有很多弱引用(WeakMap、WeakSet、WeakRef 等),被弱引用吊着的对象理论上不算泄漏,直接排除,能少一大半误报。
然后再把相似的对象聚成一类(同类 + 同名 + 同引用链 = 一个 Cluster),比如:
VideoFeedViewModel:占 120 MB,出现 340 次
代表引用链:VideoFeedViewModel -> FeedCache -> GlobalMap -> GlobalEnv

这样,你就不用再面对几百万个节点,眼前只剩一批“重点嫌疑对象”。
四、跨版本对比:给对象贴“新增 / 增长 / 公共”标签
把新旧两份总榜对齐,对象就被分成三类:
•新增:旧版没有、新版才出现。如果它又大、又常出现、还指向长生命周期的根,高度可疑。
•增长:新旧都有,但新版明显变大。多半是泄漏加重、缓存失控,或者忘了清理。
• 公共运行时:Array、Map、String 这类基础对象变大,本身不一定算业务泄漏,但背后要是挂着一堆业务对象,还得继续追。
排查优先级也简单:先看“新增且变大的业务对象”,再看“明显变大的业务对象”,最后才轮到公共对象和偶尔冒个头的对象。
五、看懂引用链,找到“决定性一跳”
内存泄漏的核心不是看对象名,而是看引用链。就像多米诺骨牌,关键是找出“哪一跳断开,对象就能被回收”。
VideoItem -> FeedCard -> FeedCache.map -> GlobalEnv
真正可疑的不是 VideoItem,而是 FeedCache.map 这个缓存容器:如果它在页面销毁、账号切换时没人清理,就会一直拽着整条业务对象链不放。
另外两个信号要特别警惕:
• Detached DOM:名字带“Detached”、还被强引用、又占内存,基本就是泄漏。
•短命对象被长命容器持有:本该随页面销毁,却挂在全局 window / Global 上。
六、看根节点,分清“谁的锅”
顺着引用链走到尽头,最关键的是看离 GC Root 最近的那个节点(distance=1)。它决定了是“我们自己的 ArkTS 代码”还是“Native 插件 / 框架”的责任:
• ROOT_VM(ArkTS 侧):挂在模块、globalThis、GlobalEnv 上——多是我们自己的代码。
• ROOT_GLOBAL_HANDLE / ROOT_LOCAL_HANDLE(Native 侧):挂在 napi_ref、GlobalHandle 上——多是 Native 插件的问题。
一句话:ArkTS 侧泄漏靠“断开引用”,Native 侧泄漏靠“成对创建与释放”。
七、怎么修:对症下药
• ROOT_VM:页面销毁、账号切换时主动置空;给缓存加容量上限和淘汰机制;别在顶层模块 import 时就初始化大数据。
• ROOT_FRAME:检查长生命周期的 async 函数、Promise 是不是一直没结束;generator 是不是长期暂停。
• ROOT_LOCAL_HANDLE:napi_open_handle_scope / napi_close_handle_scope 必须成对出现。
•ROOT_GLOBAL_HANDLE(最常见):napi_create_reference要配napi_delete_reference;setInterval用完要clearInterval——总之“创建一对,释放一对”。
八、一个完整例子
新版对比报告里冒出个 Top1:LiveRoomViewModel,占 180 MB,旧版是 0(纯新增),5 份快照全都出现。
引用链:LiveRoomViewModel -> LiveRoomContext -> LiveRoomCache.map -> GlobalEnv
根节点是 GlobalEnv,命中 ROOT_VM,责任在我们自己的 ArkTS 代码。决定性一跳是 LiveRoomCache.map -> LiveRoomViewModel:全局 map 一直拽着 ViewModel 不放。
修复就三件事:直播间退出时清掉 LiveRoomCache;别让全局 map 直接持有 ViewModel,改存轻量 ID 或用弱引用;给缓存加容量上限和淘汰策略。
如果根因落在 GlobalHandle,那就再关联 Native 调用栈,找到是哪段 Native 代码创建了引用,按“配对释放”原则修。
九、总结
jsleak-analysis 的多版本快照对比聚类,本质上是一套排查方法,而不是“跑一下工具”那么简单:

•单快照先聚类,算强引用链,先把弱引用造成的误报排除;
•组内多快照合并,过滤偶发波动,抓住持续存在的问题;
•跨版本“新增 / 增长 / 公共”对比,锁定回归点;
•读懂引用链,找到“断开即释放”的关键一跳;
•看 distance=1 根节点,分清 ArkTS 还是 Native 的责任;
•按故障模式对症下药。
记住一个原则就够了:别被一两个大对象吓到,也别被海量对象淹没。先看趋势、再看增长;先看业务对象、再看公共对象;先看强引用链、再看根节点——最后,找到那条“一剪就断”的持有关系。
----------------------------------------------------------------------------------------------------------

🔗 社区DFX专题文章: https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001?ha_source=zxqy-IT&ha_sourceId=89000468


【扫码加入 HarmonyOS DFX 技术交流群】
相关推荐
codezi
302
0
天树
2445
0
威哥爱编程
4374
0
张子涵
2174
0
周正
3550
0
鸿蒙小助手
致力于为鸿蒙开发者谋福利
帖子
提问
粉丝
鸿蒙应用问题定位定界DFX Skill 五大实战,轻松掌握故障定界与修复
2026-10-21 15:39:59 发布FFRT阻塞导致冻屏的分析链路复盘
2026-10-21 15:36:35 发布


京公网安备:11010502051901号