logo
积分中心
内存泄漏怎么查?JSLeak 教你用“新旧快照对比”揪出元凶
头像 鸿蒙小助手 2026-10-21 15:46:00    发布
34 浏览 3 点赞 1 收藏

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:

内存泄漏怎么查?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 的责任;

  •按故障模式对症下药。

记住一个原则就够了:别被一两个大对象吓到,也别被海量对象淹没。先看趋势、再看增长;先看业务对象、再看公共对象;先看强引用链、再看根节点——最后,找到那条“一剪就断”的持有关系。

----------------------------------------------------------------------------------------------------------

🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5?ha_source=zxqy-IT&ha_sourceId=89000468

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

【扫码加入 HarmonyOS DFX 技术交流群】

©本站发布的所有内容,包括但不限于文字、图片、音频、视频、图表、标志、标识、广告、商标、商号、域名、软件、程序等,除特别标明外,均来源于网络或用户投稿,版权归原作者或原出处所有。我们致力于保护原作者版权,若涉及版权问题,请及时联系我们进行处理。
分类
HarmonyOS
地址:北京市朝阳区北三环东路三元桥曙光西里甲1号第三置业A座1508室 电话:13391790444或(010)62178877
版权所有:电脑商情信息服务集团 北京赢邦策略咨询有限责任公司
声明:本媒体部分图片、文章来源于网络,版权归原作者所有,我司致力于保护作者版权,如有侵权,请与我司联系删除

京ICP备:2022009079号-2

京公网安备:11010502051901号

ICP证:京B2-20230255