HarmonyOS 7 HiDebug + Memory Profiler 实战:从内存上涨到 Native/GPU 泄漏定位
页面退出去又回来,内存涨了 200MB 不下来——这不是 GC 慢,是真的漏了。

线上收到反馈:有个列表页面,用户反复进出十几次之后,应用内存从 300MB 涨到了 800MB,最后直接 OOM 了。最开始以为是 ArkTS GC 没来得及回收,等了几分钟还是不下来,那就不是 GC 的问题了。
这种问题最容易犯的错就是只看 ArkTS Heap。但 HarmonyOS 的内存不是只有 JS 堆这一块,Native 内存、GPU 内存、线程、FD,任何一个地方漏了,整体 RSS 都会涨。
一、先搞清楚 HarmonyOS 的内存到底分几块
| 内存类型 | 来源 | 排查工具 |
|---|---|---|
| ArkTS Heap | JS/TS 对象 | Memory Profiler 堆快照 |
| Native Heap | C/C++ 代码、.so 库 | HiDebug、Native Heap 快照 |
| GPU Memory | Surface、Pixel、纹理 | GPU Memory 面板 |
| 线程栈 | 每个线程占的内存 | HiDebug 线程统计 |
| FD | 文件描述符、socket | HiDebug FD 统计 |
很多人内存出问题就盯着 ArkTS Heap 看,翻半天找不到泄漏点。其实很可能根本不是 JS 对象漏了,是 Native 层或者图形资源没释放。
二、第一次排查:只看 ArkTS Heap,没找到问题
先抓了一个 ArkTS Heap 的快照,对比两次进入页面后的对象数量。列表数据、图片对象、页面实例,看起来数量都是正常的——每次退出之后页面实例都被回收了,没有明显的泄漏对象。
这时候就容易卡住:JS 堆看起来没问题,那内存涨去哪了?
三、用 HiDebug 把内存拆成明细看
HiDebug 提供了更细粒度的内存统计,可以直接看到每一块占了多少。
这段代码解决什么问题: 采集应用内存的各模块明细数据。
文件: utils/MemoryMonitor.ets
用途: 线上内存埋点
接入位置: 页面生命周期、定时上报
import hiDebug from '@ohos.hidebug';
function getMemoryDetail() {
const pid = process.pid;
const memo = hiDebug.getAppMemorySize(pid);
return {
nativeSize: memo.nativeSize,
heapSize: memo.heapSize,
graphicsSize: memo.graphicsSize,
stackSize: memo.stackSize,
rssSize: memo.rssSize,
fdSize: memo.fdSize
};
}
跑了几次页面进出之后把数据打出来,发现 nativeSize 每次都在涨,graphicsSize 也涨了一些,但 heapSize 基本稳定。问题不在 ArkTS 层,在 Native 和图形层。
四、Native 内存为什么会涨
Native 内存是 C/C++ 代码分配的堆内存。如果你的应用用了 Native 库、做了图片解码、操作了底层缓冲区,这些内存都在 Native 堆上。
ArkTS 的 GC 管不到 Native 内存。JS 对象被回收了,不代表它持有的 Native 资源也被释放了。很多人写代码的时候只记得 image.release(),但 Native 层的缓冲区、解码器上下文、临时内存块,一个没释放就一直在那。
| 常见 Native 泄漏点 | 原因 |
|---|---|
| NativeBuffer 未释放 | 创建了没调用 release |
| Image/PixelMap 未释放 | 图片对象退出时没回收 |
| 解码器上下文残留 | 视频/图片解码器没销毁 |
| 第三方 Native 库 | 库内部自己的内存泄漏 |
五、GPU 内存为什么也在涨
graphicsSize 涨了,说明图形资源有残留。最常见的就是 Surface 和 PixelMap 没释放。
页面里有个图片列表,每次进入页面都会创建新的图片对象。退出的时候只把 ArkTS 层的引用清了,但 Native 层的 PixelMap 资源还在,因为没有显式调用释放。反复进出十几次,十几套图片资源都留在 GPU 内存里了。

六、线程和 FD 泄漏容易被忽略
除了内存块,还有两个东西特别容易漏:线程和文件描述符。
| 资源类型 | 泄漏表现 | 排查方法 |
|---|---|---|
| 线程 | 反复操作后线程数越来越多 | hiDebug.getThreadList() |
| FD | 文件/socket 句柄不释放 | hiDebug.getFDLimit() |
页面退出的时候如果有个线程一直在跑、没退出,那这个线程占的栈内存就一直留着。如果打开了文件或者连接没关,FD 也会一直涨。
这些问题在 ArkTS Heap 快照里完全看不到,必须用 HiDebug 单独查。
七、完整的内存泄漏排查流程
最后整理了一套比较完整的排查流程:
- 先抓整体内存趋势:看 RSS 是不是真的持续上涨;
- 用 HiDebug 拆分明细:看是 Heap、Native、GPU、线程还是 FD 哪一块在涨;
- 对应抓对应类型的快照:Heap 涨就抓 ArkTS 堆快照,Native 涨就抓 Native 堆快照;
- 对比两次快照的差异:找哪些对象/资源数量一直在涨;
- 定位到具体代码:找到创建这些资源的位置,检查有没有对应的释放逻辑;
- 验证修复:改完之后反复进出页面,再看内存曲线是不是平稳了。
八、几个容易踩的坑
第一个坑:只看 ArkTS Heap。JS 堆没问题不代表内存没问题,Native 和 GPU 资源才是泄漏重灾区。
第二个坑:把 RSS 不下降当成 GC 失效。RSS 是整个进程的内存,JS GC 回收了 JS 堆,但 Native 内存、图形内存、页缓存都在 RSS 里,这些不会因为 JS GC 就降下来。
第三个坑:测试时间过短。内存泄漏是累积的,进出一两次看不出来,反复进出十几次甚至几十次才明显。测试的时候要模拟用户真实使用场景。

这次排查完最大的体会是:内存问题不是"看 JS 堆就行"。HarmonyOS 的内存是分层的,ArkTS、Native、GPU、线程、FD 各有各的生命周期。哪一层漏了整体内存都会涨。排查的时候先拆分明细,定位到哪一层出问题,再针对性找泄漏点,不要一上来就翻 JS 堆找对象。
更多推荐


所有评论(0)