页面退出去又回来,内存涨了 200MB 不下来——这不是 GC 慢,是真的漏了。

文章封面

线上收到反馈:有个列表页面,用户反复进出十几次之后,应用内存从 300MB 涨到了 800MB,最后直接 OOM 了。最开始以为是 ArkTS GC 没来得及回收,等了几分钟还是不下来,那就不是 GC 的问题了。

这种问题最容易犯的错就是只看 ArkTS Heap。但 HarmonyOS 的内存不是只有 JS 堆这一块,Native 内存、GPU 内存、线程、FD,任何一个地方漏了,整体 RSS 都会涨。

一、先搞清楚 HarmonyOS 的内存到底分几块

内存类型来源排查工具
ArkTS HeapJS/TS 对象Memory Profiler 堆快照
Native HeapC/C++ 代码、.so 库HiDebug、Native Heap 快照
GPU MemorySurface、Pixel、纹理GPU Memory 面板
线程栈每个线程占的内存HiDebug 线程统计
FD文件描述符、socketHiDebug 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 单独查。

七、完整的内存泄漏排查流程

最后整理了一套比较完整的排查流程:

  1. 先抓整体内存趋势:看 RSS 是不是真的持续上涨;
  2. 用 HiDebug 拆分明细:看是 Heap、Native、GPU、线程还是 FD 哪一块在涨;
  3. 对应抓对应类型的快照:Heap 涨就抓 ArkTS 堆快照,Native 涨就抓 Native 堆快照;
  4. 对比两次快照的差异:找哪些对象/资源数量一直在涨;
  5. 定位到具体代码:找到创建这些资源的位置,检查有没有对应的释放逻辑;
  6. 验证修复:改完之后反复进出页面,再看内存曲线是不是平稳了。

八、几个容易踩的坑

第一个坑:只看 ArkTS Heap。JS 堆没问题不代表内存没问题,Native 和 GPU 资源才是泄漏重灾区。

第二个坑:把 RSS 不下降当成 GC 失效。RSS 是整个进程的内存,JS GC 回收了 JS 堆,但 Native 内存、图形内存、页缓存都在 RSS 里,这些不会因为 JS GC 就降下来。

第三个坑:测试时间过短。内存泄漏是累积的,进出一两次看不出来,反复进出十几次甚至几十次才明显。测试的时候要模拟用户真实使用场景。

运行效果图

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐