本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:ArkTS 九种泄漏模式的排查指南(上)-华为开发者话题 | 华为开发者联盟
前言
你有没有这样的经历?
辛辛苦苦写好的鸿蒙应用,在测试环境跑得好好的,一上现网就开始"抽风"——搜索页面偶尔闪退、联系人列表越用越卡、签到功能用久了直接崩溃。你打开日志一看,满屏的 raw stack,文件名和行号倒是有了,可到底是谁在背后搞鬼?一头雾水。
别急,你不是一个人。我们团队在一次现网闪退排查中,对着源码一行行啃,硬是从一堆"莫名其妙"的崩溃里,归纳出了 ArkTS 内存泄漏的九种典型模式。今天,咱们就用大白话+代码,把这九种模式掰开揉碎讲清楚。
本文(上)带你认识前五种泄漏模式:
1. Observer / Callback 未移除
2. super.aboutToDisappear() 调用缺失
3. 配置类持有 ViewModel 闭包
4. 双向循环强引用
5. 路由参数持有页面闭包
一、先搞懂一个概念:内存泄漏到底在"漏"什么?
内存泄漏的本质,其实就一句话:一个本该"退休"的对象,被一个"长命"的对象死死拽着,死活不让它走。
想象一下,你租了一间房子(短生命周期对象),租期到了该搬走,结果房东(长生命周期对象)把你的钥匙藏了起来,还跟物业说"这人还没搬呢"。于是你的东西一直堆在屋里,房间永远腾不出来——这就是内存泄漏。
排查时,你只需要反复问自己一个问题:
"谁还持有它?"
沿着这条引用链一路往上追,找到那个"长命"的持有方,然后在合适的时机把引用切断依次搞定。
二、模式一:Observer / Callback 未移除——"注册了却忘了退群"
鸿蒙的生命周期框架靠"观察者列表"来感知页面状态变化。你调用 addObserver 注册了一个观察者,相当于加了一个群。可问题是——页面销毁时,你有没有退群?
如果没有对称地调用 removeObserver,框架就会通过观察者列表一直持有你的页面实例。页面虽然已经从视图树上消失了,但在框架眼里它"还活着",GC 自然不敢回收。
🔍 问题根因: addObserver 注册后,页面销毁时没有对称地调用 removeObserver,框架通过观察者列表持续持有页面实例。

🔀 同类变体:子组件销毁未透传
当父组件持有多个子 Tab 时,只有"当前激活的 Tab"的 aboutToDisappear() 会被系统调用。那些没被激活的 Tab,就像被遗忘在角落的租客——它们的回调永远不会被移除。

三、模式二:super.aboutToDisappear() 调用缺失——"断链的多米诺骨牌"
ArkTS 中子类重写 aboutToDisappear 时,编译器并不会强制你调用 super.aboutToDisappear()。这就像接力赛——你跑完了自己那一棒,却忘了把接力棒交给下一位选手。
一旦漏调,继承链上所有基类积累的清理逻辑都会静默跳过。
adapter、loadMoreHelper、childViewModels……这些资源就像多米诺骨牌一样,从你漏调的那一环开始,全部倒下。
🔍 问题根因: 子类重写 aboutToDisappear 时忘记调用 super,继承链上所有基类的清理逻辑全部跳过。

💡 记住一个原则: super.aboutToDisappear() 永远放在 aboutToDisappear 的最后——子类先清理自己,再触发基类清理链。
四、模式三:配置/缓存类持有 ViewModel 闭包——"被遗忘的抽屉"
业务中经常有这样的场景:把 ViewModel 的方法作为回调注入到 Config/Helper 对象中。Config 对象把这些闭包像文件一样塞进抽屉里缓存起来,可问题是——页面退出时,有没有人去翻这个抽屉?
如果没有提供"清理"入口,Config 侧向 ViewModel 侧的引用就永远断不开。页面虽然销毁了,但 Config 抽屉里的闭包还紧紧抓着 ViewModel 不放。
🔍 问题根因: Config 对象缓存了 ViewModel 的闭包,但页面退出时没有清理入口,引用无法断开。

五、模式四:双向循环强引用——"互相牵手的两个人"
两个对象互相持有对方的强引用,就像两个人手拉手转圈。
JavaScript/ArkTS 的 GC 确实能回收循环引用——但前提是这整个圈子"没人认识"。一旦圈子中任意一端被外部强引用锚定(比如 @State、@Observed、全局变量),整个环就长期存活了。
🔍 问题根因: 两个对象互相持有强引用形成闭环,且环中至少一端被外部锚定,GC 无法回收。

🔀 同类变体:ViewModel ↔ Config 的双向持有
ViewModel 持有 Config,Config 又持有 ViewModel 的闭包——同样是双向循环。修复思路一样:在任意一方销毁时,置空对方引用即可打破环。

六、模式五:路由参数持有页面闭包——"被框架扣为人质"
路由框架在页面退栈后,并不会立刻释放路由参数对象(PageParam),而是延迟释放。如果你的 PageParam 里塞了页面的箭头函数字段,而该函数词法捕获了页面的 this——恭喜你,页面被路由框架间接"扣为人质"了。
🔍 问题根因: 路由框架延迟释放 PageParam,PageParam 中的箭头函数词法捕获了页面的 this,页面被间接延迟持有。

七、下篇预告
上篇我们聊了五种泄漏模式,下篇将继续揭晓剩下的四种"内存刺客":
6. 全局静态集合残留——"进程维度的记忆宫殿"
7. 箭头函数跨组件持有 this——"父组件的影子"
8. 原生/框架资源未 dispose——"跨平台的幽灵"
9. NAPI 跨层闭包泄漏——"C++ 端的定时炸弹"
最后还会附上五步排查方法论总结,帮你建立一套系统的内存泄漏排查思路。敬请期待!
—— 未完待续,下篇更精彩 ——
🔗 官网开发者学堂视频:https://developer.huawei.com/consumer/cn/training/result?type2List=201783644516849879&orderBy=1&courseType=5

🔗 社区 DFX 专题文章: https://developer.huawei.com/consumer/cn/forum/subject/2101218731402391001


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





