写点什么

资源泄漏采集开发实践

  • 2026-08-03
    北京
  • 本文字数:5533 字

    阅读完需:约 18 分钟

本原创文章帖发布在华为开发者联盟社区,欢迎开发者前往访问评论交流,更多与该内容相关讨论,请点击原帖查看:资源泄漏采集开发实践-华为开发者话题 | 华为开发者联盟

概述

资源调优和资源泄漏是应用开发中常见且难以快速解决的两类棘手问题。在鸿蒙应用开发过程中,这些问题通常难以利用常规调试手段(如 hilog 流水日志、/proc/[pid]/smaps、/proc/meminfo、/proc/memview、hidumper --mem [pid])直接定位和处理。

为快速解决上述两类痛点问题,鸿蒙系统通过 Performance Analysis Kit 开放的 HiDebug 资源采集接口(OH_HiDebug_StartProfiler/OH_HiDebug_StopProfiler),提供线上资源分配栈采集功能,赋能开发者高效实现问题的自诊断与自闭环。

该资源采集接口性能功耗开销较大,盲目或高频调用易触发应用卡顿、发热。集成时,请务必谨慎评估开销,建立严格的条件触发策略和配置合理的采集参数,兼顾线上功能正常运行和性能开销的平衡。

本文将介绍以下内容:

  • HiDebug 资源采集简介

    资源泄漏检测流程

    场景案例

实现原理

HiDebug 资源采集简介

为帮助开发人员快速定位进程内的资源泄漏问题,HarmonyOS 提供了 HiDebug 资源采集能力(HiDebug API 参考 ),开发者可调用 OH_HiDebug_StartProfiler / OH_HiDebug_StopProfiler 接口,针对指定资源类型启动采集,在采集窗口内记录资源分配的调用栈与统计信息,并将结果输出为.htrace 文件。

开发者可将生成的.htrace 文件导入 DevEco Studio Profiler 进行关联分析,按调用栈分组统计,频率最高的调用栈即为泄漏嫌疑点,从而加速定位泄漏代码位置,降低定界定位成本。

OH_HiDebug_StartProfiler 支持以下资源类型:

资源泄漏通常是因为资源被分配后未在生命周期结束时正确释放,常见原因包括:

   

• FD 泄漏:open/fopen、epoll、eventfd、socket、pipe、dup 等创建的 fd 打开后,未调用 close。

   

• 线程泄漏:pthread / std::thread 创建后未正确 join / detach,或线程函数未正常退出。

   

• Native 内存泄漏:通过 malloc/calloc/realloc/new 分配的堆内存(NativeHeap)未 free / delete; 通过 mmap 映射的映射区内存(包含 so 等文件映射以及匿名映射)未 munmap。

   

• GPU 内存泄漏:纹理、缓冲区等 GPU 资源创建后未释放。

   

• 全局句柄泄漏:Native 层对 ArkTS 对象创建了持久化强引用(如 napi_create_reference)后未 napi_delete_reference。

生成文件类型介绍

错误码

OH_HiDebug_StartProfiler 与 OH_HiDebug_StopProfiler 返回 HiDebug_ErrorCode,取值如下(HIDEBUG_RES_PROF_SUCCESS 与 HIDEBUG_RES_PROF_FAILURE 为两接口共有,HIDEBUG_RES_PROF_NOT_STARTED 仅 StopProfiler 返回,其余仅 StartProfiler 返回):

资源泄漏检测流程

应用启动后,通过 NAPI 拉起 ResourceProfilerManager 单例。

   • 触发采集的三条路径(择一执行,且任一时刻仅允许一路采集):

(1) C++ 统一轮询:后台线程按各自周期检测 FD / Thread / Native / GPU 四类资源(Native 每 200s、其余每 60s);Native 与 GPU 需连续 2 次超阈值、FD 与 Thread 单次超阈值即自动触发对应类型的采集,避免偶发波动误触发。

说明: 以上 Native 内存和 GPU 内存触发采集条件推荐值均基于 12GB RAM 设备,其他 RAM 大小设备采集阈值请开发者自行根据实际情况调整。

(2) ArkTS 定时检测:ArkTS 侧通过 setInterval 周期检测 VM 堆内存使用率,超过阈值即触发 Global Handle 采集。

(3) 手动按需采集:在可疑业务函数执行前后调用 OH_HiDebug_StartProfiler/ OH_HiDebug_StopProfiler,采集该时间窗口内的资源分配数据。

  • OH_HiDebug_StartProfiler 接口调用后,采集器会记录资源分配数据。

    采集结束(调用 OH_HiDebug_StopProfiler 或 maxDuration 到期自动停止)后,系统生成.htrace 文件并通过回调返回其沙箱路径。

    将.htrace 文件导入 DevEco Studio Profiler,按调用栈分组分析,定位泄漏点。

场景案例

场景描述

开发人员观测到应用进程的 Native 内存持续增长、或/proc/self/fd 条目数异常增多、或/proc/self/status 中 Threads 值持续上升,需要定位是哪段代码在泄漏资源、并分析其调用栈。

开发步骤

1. 添加依赖

Native 侧在 CMakeLists.txt 中链接 HiDebug NDK:

target_link_libraries(entry PUBLIC libace_napi.z.so ohhidebug.so)
复制代码

ArkTS 侧引入 PerformanceAnalysisKit 与 Native 模块:

import { hilog, hidebug } from '@kit.PerformanceAnalysisKit';import resourceProfiling from 'libentry.so';
复制代码

2. 接入资源采集管理器

ResourceProfilerManager 是对 OH_HiDebug_StartProfiler / OH_HiDebug_StopProfiler 的单例封装,统一管理采集状态与回调,对外暴露采集接口与统一轮询接口。其公开接口签名如下:

class ResourceProfilerManager {public:    static ResourceProfilerManager &GetInstance()    {        static ResourceProfilerManager instance;        return instance;    }    // Basic profiling interface with custom config    HiDebug_ErrorCode StartProfiling(        OH_HiDebug_ResourceType type,        OH_HiDebug_ResProfilerConfig& config,        std::function<void(OH_HiDebug_ProfilingResult*)> userCallback);    // Basic profiling interface with default config    HiDebug_ErrorCode StartProfiling(        OH_HiDebug_ResourceType type,        std::function<void(OH_HiDebug_ProfilingResult*)> userCallback);    HiDebug_ErrorCode StopProfiling();    bool IsProfiling() const;    // Unified polling detection interface    // Polls multiple resource types; triggers profiling for the matching type    // when any one exceeds its threshold.    // Skips triggering if a profiling task is already running.    void StartUnifiedPolling(        std::function<void(const char* resultPath, OH_HiDebug_ResourceType type)> onResult);    void StopUnifiedPolling();    bool IsPolling() const;private:    static void InternalProfilingCallback(OH_HiDebug_ProfilingResult* result);    void PollingLoop(std::function<void(const char* resultPath, OH_HiDebug_ResourceType type)> onResult);    std::atomic<bool> m_isProfiling{false};    std::function<void(OH_HiDebug_ProfilingResult*)> m_userCallback;    std::atomic<bool> m_isPolling{false};    std::thread m_pollingThread;};
复制代码

OH_HiDebug_ResProfilerConfig 各参数含义如下,默认配置已在开发实践示例代码中内置,开发者亦可按场景自定义:

完整实现(含轮询主循环、阈值检测、回调处理)参考示例代码。

3. 启动统一轮询检测

调用 startUnifiedPolling 即可启动 C++ 后台线程,对 FD / Thread / Native / GPU 四类资源按各自周期轮询(Native 每 200s、其余每 60s);Native 与 GPU 需连续 2 次超阈值、FD 与 Thread 单次超阈值即自动触发采集,并通过回调返回结果文件路径:

// Start polling with a callbackresourceProfiling.startUnifiedPolling((path: string, type: number) => {  let typeName = 'Unknown';  switch(type) {     case 0: typeName = 'FD'; break;     case 1: typeName = 'Thread'; break;     case 2: typeName = 'Native Mem'; break;     case 3: typeName = 'GPU Mem'; break;     case 4: typeName = 'Global Handle'; break;    default: typeName = `Type ${type}`; break;  }  const msg = `[${typeName}] Profiling Finished!\\nPath: ${path}`;  hilog.info(0x3300, 'ResourceProfiling', msg);});
复制代码

统一轮询内置阈值与触发策略:FD 数量 5000 个、线程数 700 个、Native 内存 3GB、GPU 内存 2.3G;Native 每 200s 轮询且需连续 2 次超阈值、GPU 每 60s 轮询且需连续 2 次超阈值、FD 与 Thread 每 60s 轮询且单次超阈值即触发采集。

4. ArkTS 侧 VM Heap 检测

Global Handle 泄漏由 ArkTS 侧独立检测。通过 hidebug.getAppVMMemoryInfo()获取 VM 堆信息,当已使用占比超过 70%时触发 Global Handle 采集:

// VM heap memory detection threshold (70%)const VM_HEAP_THRESHOLD = 0.7;let vmTimer: number = -1;function detectVMHeapLeak(onResult: (path: string) => void): void {  try {    const vmInfo = hidebug.getAppVMMemoryInfo();    const heapUsed = hidebug.getAppVMObjectUsedSize();    if (vmInfo.totalHeap > 0 && (heapUsed / vmInfo.totalHeap) > VM_HEAP_THRESHOLD) {       // Call NAPI startProfiling with type 4 (OH_RES_TYPE_GLOBAL_HANDLE)       const ret: number = resourceProfiling.startProfiling(4, 180, 256, 30, 384, 10);      if (ret === 0) { // HIDEBUG_RES_PROF_SUCCESS        // Set timeout to stop profiling after 180s        setTimeout(() => {          resourceProfiling.stopProfiling();        }, 180000);      }    }  } catch (e) {    hilog.error(0x3300, 'ResourceProfiling', 'VM heap detection failed: %{public}s', JSON.stringify(e));  }}// Start periodic VM heap memory detection (60s)vmTimer = setInterval(() => {  detectVMHeapLeak((path: string) => {    const msg = `[Global Handle] VM Heap Exceeded!\\nPath: ${path}`;    hilog.info(0x3300, 'ResourceProfiling', msg);  });}, 60000);
复制代码

5. 手动按需采集

当能定位到可疑的业务函数时,可在其执行前后手动启停采集,精确覆盖该业务窗口内的资源分配:

// Manually start profiling for FD (type=0)resourceProfiling.startProfiling(0, 30, 256, 30, 384, 10);// ...execute the suspicious business logic (profiler records allocations after Start)resourceProfiling.stopProfiling();
复制代码

6. 文件导出

采集完成后,回调中的 result->filePath 即为结果文件沙箱路径,格式如下:

/data/storage/el2/base/files/<类型>-<被采集应用进程名>-<进程号>-<时间戳>.htrace

示例:/data/storage/el2/base/files/native-com.example.resourceleakprofilingdemo-33367-20260708_113011.htrace

文件导出:参考应用沙箱路径和真实物理路径的对应关系

7. 关闭检测

抓取到所需维测数据后,停止轮询与定时检测:

// Stop polling and VM detection when the page is destroyedif (vmTimer !== -1) {  clearInterval(vmTimer);}resourceProfiling.stopUnifiedPolling();
复制代码

8. 分析生成的文件

将.htrace 文件导入 DevEco Studio Profiler 后,按调用栈分组统计,出现频次最高的调用栈即为泄漏嫌疑点,据此跳转到对应源码位置完成修复,可参考内存分析介绍。

分析要点:

   • 以调用栈为维度分组,关注高频出现的栈。

   • 同一栈反复出现,说明对应代码路径存在资源分配未释放。

   • 结合业务逻辑确认该路径是否缺少对应的释放调用。

采集规格约束

采集配额约束

采集配额分整机与应用两级管控:

• 整机级(所有应用共享):每日最多采集 4 次;同一时刻最高支持 4 个不同应用并行采集。

• 应用级(单应用独享):每日最多采集 2 次;同一时刻最高支持应用内 2 个进程并行采集。

超出配额时返回 HIDEBUG_RES_PROF_DAILY_QUOTA_EXCEEDED 或 HIDEBUG_RES_PROF_PROCESS_OVERLIMIT。

说明:多个应用进程并行采集会对整机性能产生较大影响,请开发者根据实际情况谨慎评估后使用。

采集负载熔断机制

当系统触发以下任一负载保护条件时,采集请求将被拒绝并返回相应错误码:

• CPU 限制:整机系统 CPU 占用率超过 70%,返回 HIDEBUG_RES_PROF_CPU_OVERLOADED。

• RAM 限制:整机剩余可用内存(RAM)低于 2GB,返回 HIDEBUG_RES_PROF_MEM_PRESSURE_CRITICAL。

• ROM 限制:整机剩余可用存储空间(ROM)低于 15GB 与整机总量 3%中的较大值,返回 HIDEBUG_RES_PROF_STORAGE_PRESSURE_CRITICAL。

采集并发冲突约束

本 API 与开发者工具( DevEco Studio Profiler/hiprofiler_cmd)或系统采集任务存在排他性,发生冲突时采集请求将被拒绝并返回 HIDEBUG_RES_PROF_CONFLICT,使用前先退出相关工具或任务。

采集文件老化管控

每种资源类型的采集文件均受以下滚动老化约束:

   • 按文件个数:只保留最新 5 份文件,超出后滚动删除最早文件。

   • 按单文件大小:单文件上限 1GB。

   • 按目录空间:沙箱中采集文件存储空间超过 4GB 时,删除最早的采集文件。

   • 按存储时效:每个采集文件最大存储时长 24 小时,到期后清理。

采集性能功耗与触发策略

受上述采集规格与约束限制,本 API 不保证采集请求一定成功。调用本 API 会对当前应用进程及整机性能功耗产生不可忽略的影响,包括但不限于 CPU、内存占用升高,丢帧卡顿、发热。集成时必须谨慎评估性能功耗开销,建立严格的条件触发策略(如仅在特定灰度范围内、应用资源超基线阈值时触发,并配置合理的采集参数以兼顾性能功耗平衡)后再决策是否开启。严禁在生产环境下盲目或高频次触发。

采集接口调用约束

• OH_HiDebug_StartProfiler/OH_HiDebug_StopProfiler 配对:未配对调用会导致 HIDEBUG_RES_PROF_ALREADY_STARTED,确保一次采集完整覆盖一个业务场景后再启动下一次。

• 采集时间窗口语义:采集器仅记录 OH_HiDebug_StartProfiler 之后的资源分配数据,OH_HiDebug_StartProfiler 前已分配的资源不会被采集,因此接口调用采集建议包裹完整业务时间窗口。

示例代码

• 资源泄漏采集示例


🔗 官网开发者学堂视频: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 技术交流群】