JsLeakWatcher 报出一批对象,真的是内存泄漏吗:页面缓存、监听器和可达路径怎么分
页面退出后 JsLeakWatcher 报出几十个对象,团队立刻把所有缓存清掉,结果二次进入变慢,告警却仍然存在。工具发现“对象仍可达”,并不会自动替你判断它应该继续存在多久。缓存、异步任务和忘记解绑的监听器都可能保留页面对象,处置方式完全不同。
验证范围:官方页面用于确认 HarmonyOS 7/API 26 的能力方向;文章代码只验证应用侧状态、预算或一致性模型。当前电脑是 API 24 SDK,且没有连接 HarmonyOS 7 真机,所以下面的断言不是 API 26 编译、设备性能或线上效果证明。
先观察退出后的收敛曲线
连续进入退出同一页面 10 次,每次等待固定时间并记录页面实例数、监听器数和缓存条目。若实例在缓存上限内稳定,是受控保留;若每轮增加且根路径来自全局监听器,则更像真实泄漏。
案例一:有上限的页面缓存可以保留
保留最近两个页面以提升返回速度,淘汰时对象数量回落,属于明确策略。需要把缓存上限和淘汰事件写进证据。
案例二:全局事件监听器让页面永不释放
页面创建时订阅全局事件,退出时未解除;每轮都会新增回调并捕获页面状态。即使清空数据缓存,页面仍然可达。
可达不等于应该永久存在
泄漏判断需要“本应释放的对象没有释放”这一业务前提。只看一次快照会把缓存当泄漏;只看总内存又可能漏掉缓慢增长的监听器链。
typeSample={round:number;pageInstances:number;listeners:number};functiongrowing(samples:Sample[],key:'pageInstances'|'listeners'){returnsamples.every((x,i,a)=>i===0||x[key]>=a[i-1][key])&&samples.at(-1)![key]>samples[0][key];}consts=[{round:1,pageInstances:1,listeners:1},{round:2,pageInstances:2,listeners:2},{round:3,pageInstances:3,listeners:3}];if(!growing(s,'listeners'))thrownewError('增长链没有识别');这段模型用确定输入验证“重复进入退出后识别持续增长而非一次性保留”。它适合先发现边界错误,但不会冒充平台接口或设备性能测试。
清缓存还是修监听器
| 方案 | 适用范围 | 主要风险 |
|---|---|---|
| 看到告警就清空 | 临时止损 | 性能退化且可能没修根因 |
| 只看内存总量 | 粗粒度监控 | 无法定位可达路径 |
| 快照加收敛序列 | 正式排障 | 测试周期更长 |
以可达路径定位根引用,再用重复序列验证是否收敛。缓存要证明上限和淘汰,监听器要证明解绑和实例回落。
建立可解释的保留账本
维护RetentionLedger,登记缓存、订阅、定时器和异步任务的拥有者、释放时机及上限。工具告警先与账本对照,再决定修复。
修复后重复同一进入退出序列
- 页面重复进入退出至少十轮。
- 缓存上限和淘汰事件可观察。
- 监听器数量不会逐轮增长。
- 异步任务结束后不再持有页面。
- 修复前后使用同一脚本比较。
内存工具提供线索,不提供业务结论。把对象为什么活着、何时该死记录清楚,才能既修掉泄漏,又不误伤有价值的缓存。
官方资料
- HarmonyOS 7 新能力
- 2026 年 9 月开发者月刊
- HarmonyOS 应用开发指南