HarmonyOS鸿蒙Next中ArkTS内存泄漏案例及常见问题

HarmonyOS鸿蒙Next中ArkTS内存泄漏案例及常见问题

一、内存泄漏案例

1.1 source_text_module_record(SourceTextModule)案例(无法释放root通过断开引用关系解决)

概述

      该类内存泄漏问题的特点是无法释放GC root对象,只能通过断开引用关系来释放被间接持有的其他对象。

问题现象

      应用在操作固定场景后发现重复触发该场景会导致js内存占用持续增加,可能存在内存泄漏,需要定位内存泄漏根因。

分析思路

       参照js内存泄漏分析步骤(https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-arkts-js-memory-analysis

分析步骤

第一步 查看snapshot快照对比:

      参考snapshot操作文档(https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/ide-snapshot-basic-operations),将抓取到的两次snapshot文件导入IDE,通过Compeaison对比查看如下图:

图片

第二步 泄漏对象分析:

      如下图所示,将快照的内存对象按照size Delta从大到小排序,优先关注占用较大的对象。

      应用侧优先关注带路径的业务对象,这里优先看到UrlCacheLogic和NavigationPage这两个对象。应用侧确认UrlCacheLogic生命周期未结束,对象未释放符合预期,NavigationPage对象生命周期已结束,存在泄漏。

图片

第三步 查找内存泄漏根因:

      查看NavigationPage泄漏,distance为1的GC ROOT节点是SourceTextModule。SourceTextModule对象为系统侧创建对象,当应用使用export暴露对象后会被SourceTextModule对象持有,具体介绍见内存泄漏常见问题FAQ第九条。

      观察两个SourceTextModule对象,发现引用关系相同,且distance为1到distance为4的对象,他们的nodeid都相同,也就是说这些对象没有重复创建,不是泄漏。(ps:对应用来说,如果有一个对象需要频繁重复使用,那么如果每次使用后都释放就会不断重复创建释放对象,对性能有影响,将该对象声明周期延长甚至不释放,始终重复使用,可以用极小的空间换性能,且不会随着操作不断创建,那么该对象不算内存泄漏,可以保留不释放)

图片

图片

第四步 解决内存泄漏问题:

图片

      NavigationPage对象引用树中的引用关系如上图所示,distance为5的对象在重复创建,而distance为4的对象没有重复创建,可以选择断开这两个对象间的引用关系。断开引用关系后,GC时无法通过native遍历到distance为5之后的对象,则Node D之后的这些对象会被GC,泄漏问题解决。

      上述问题可以通过在distance为5的NavPageRouter对象生命周期结束后,从map中删除NavPageRouter对象,解决内存泄漏问题。

**      注意:**当应用侧使用export后,export对象无法被主动释放,而被export对象间接持有的对象也无法被GC,可能导致内存泄漏。当前的解决方法为手动断开export对象与其他对象间的引用关系,参考上述案例,断开引用关系的首要判断依据是断开重复创建对象与复用对象间的引用关系,即复用对象不会重复创建可以保留,重复创建对象要尽可能释放。

建议与总结

      综上,不是所有泄漏问题都需要释放distance为1的GC ROOT节点,若泄漏对象的GC ROOT节点nodeid相同,即根节点没有重复创建,则可以将没有重复创建的对象与重复创建的对象之间的引用关系断开,也能解决泄漏问题。

      类似需要断开引用关系的的场景还包括distance为1的GC ROOT对象为全局变量的场景,或者GC ROOT对象生命周期很长且可以复用的场景。

 

1.2 应用侧业务对象未释放案例(ArkTS侧未释放问题场景)

问题现象

      应用在新建浏览器窗口后关闭后浏览器窗口,重复操作后内存不断增加,出现内存泄漏。

分析思路

      参照js内存泄漏分析步骤(https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-arkts-js-memory-analysis)

分析步骤

第一步 内存泄漏对象分析

      在IDE中打开snapshot文件,应用确认问题场景为浏览器窗口,因此在搜索框中输入window。如下图所示,搜索结果中pafwindow对象count数量最多,优先点开references查看,根据distance找到distance为1的对象如下。

图片

第二步 查找内存泄漏根因:

      distance为1的对象被native持有无法释放,当前distance为1的对象是anonymous,代表匿名函数对象。根据对象名称中自带的路径信息,该对象代码实现所在文件为src/main/ets/Common/PafWindow.ts文件,函数代码实现的第一行为204行。找到distance为1的匿名函数对象在代码中的位置,发现是windowClass.on注册时传入的一个箭头函数,样例代码如下。这是一个很典型的问题,on注册时传入的回调函数如果直接传箭头函数,调用off函数的时候因为找不到这个箭头函数而导致无法成功解注册,而箭头函数会捕获上下文信息并绑定,所以导致了全局事件监听器持有了箭头函数进而持有了页面的上下文信息最终导致页面无法释放。

修改前代码:

async addWindowHighlightChangeListener() {
  this.windowClass.on('windowHighlightChange', (state) => {
    Logger.info(this.ABILITY_TAG, 'current window stage windowHighlightChange: ' + state);
    AppStorage.SetOrCreate(this.getAbiIityAppStorageConst(AppStorageWindowConst.WINDOW_INACTIVE), !state);
  });
}

removeWindowHighlightChangeListener() {
  if(this.windowClass) {
    this.windowClass.off('windowHighlightChange', (state) => {
      Logger.info(this.ABILITY_TAG, 'current window stage windowHighlightChange: ' + state);
    });
  }
}

第三步 解决内存泄漏问题:

      应用侧修改off时传入的箭头函数,确保off时传入的函数对象与on时传入的函数对象相同,修改后问题解决。

修改后代码:

windowHighlightChangeCallback: Callback<boolean> | undefined = (state: boolean) => {
  Logger.info(this.ABILITY_TAG, 'current window stage windowHighlightChange: ' + state);
  AppStorage.SetOrCreate(this.getAbiIityAppStorageConst(AppStorageWindowConst.WINDOW_INACTIVE), !state);
}

async addWindowHighlightChangeListener() {
  this.windowClass.on('windowHighlightChange', windowHighlightChangeCallback);
}

removeWindowHighlightChangeListener() {
  if(this.windowClass) {
    this.windowClass.off('windowHighlightChange', windowHighlightChangeCallback);
  }
}

建议与总结

      针对所有的on注册和off解注册场景,都要确保callback函数指针是同一个对象。1.忘记off了;2.没有提取出函数,箭头函数直接传入不是同一个函数对象;3.提取出的函数是局部变量,off时在另一个函数;

      类似需要ArkTS侧修改的场景还包括其他需要ArkTS侧调用对应释放接口的场景,ArkTS侧未调用释放接口或错误调用释放接口是造成内存泄漏最常见的原因。

 

1.3 native接口内存泄漏案例(C++侧未释放问题场景)

问题现象:

      伙伴反馈应用有内存泄露问题,将必现场景抽象成demo,比较demo点击前后的两次snapshot快照,存在内存泄漏;

      问题场景为在ets侧调用了Napi的一个方法,napi又回调了ets的一个Lambda表达式,通过snapshot发现,Lambda表达式没被释放

分析思路

      参照js内存泄漏分析步骤(https://developer.huawei.com/consumer/cn/doc/best-practices/bpta-arkts-js-memory-analysis)

分析步骤

第一步:内存泄漏对象分析:

      运行应用,拍摄一次snapshot,然后点击Hello World 7次,再拍摄一次snapshot,然后对比两帧快照,通过下图可以看出lamdba表达式发生了内存泄露,但是该lamdba表达式的distance为1,说明他没有被任何js对象引用,而且lamdba表达式只有一个日志打印,也没用this,ArkTS侧使用未见明显错误。

图片

      查看ets代码如下,demo在onclick中调用了一个napi函数,并回调一个lamdba表达式,但是发现lamdba表达式并没有被释放。

ets代码:

图片

      ets代码未发现错误使用的地方,可能是napi接口存在问题。查看C++接口代码,首先对jscb(ets的Lamdba表达式创建引用)创建了引用,然后创建了一个线程安全函数CallJs。后面调用了CallJs函数,CallJs函数中napi_call_function调用了ets的Lamdba表达式,调完后将引用释放。

napi接口postAtFront的具体实现:

//&nbsp;ArkTS线程的回调实现
static&nbsp;void&nbsp;CallJs(napi_env&nbsp;env,&nbsp;napi_value&nbsp;jsCb,&nbsp;void*&nbsp;context,&nbsp;void*&nbsp;data)&nbsp;{
&nbsp;&nbsp;if&nbsp;(env&nbsp;==&nbsp;nullptr)&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;return;
&nbsp;&nbsp;}
&nbsp;&nbsp;TaskRef&nbsp;*task_ref&nbsp;=&nbsp;reinterpret_cast&lt;TaskRef&nbsp;*&gt;(data);
&nbsp;&nbsp;&nbsp;&nbsp;napi_handle_scope&nbsp;scope;
&nbsp;&nbsp;&nbsp;&nbsp;napi_open_handle_scope(env,&nbsp;&amp;scope);
&nbsp;&nbsp;napi_value&nbsp;undefined&nbsp;=&nbsp;nullptr;
&nbsp;&nbsp;napi_get_undefined(env,&nbsp;&amp;undefined);

&nbsp;&nbsp;napi_value&nbsp;result;
&nbsp;&nbsp;auto&nbsp;callback_ref&nbsp;=&nbsp;task_ref-&gt;ref;
&nbsp;&nbsp;napi_get_reference_value(env,&nbsp;callback_ref,&nbsp;&amp;result);
&nbsp;&nbsp;napi_call_function(env,&nbsp;undefined,&nbsp;result,&nbsp;0,&nbsp;nullptr,&nbsp;nullptr);
&nbsp;&nbsp;uint32_t&nbsp;ref_count&nbsp;=&nbsp;0;
&nbsp;&nbsp;napi_reference_unref(env,&nbsp;callback_ref,&nbsp;&amp;ref_count);
&nbsp;&nbsp;napi_delete_reference(env,&nbsp;callback_ref);
&nbsp;&nbsp;delete&nbsp;task_ref;
&nbsp;&nbsp;&nbsp;&nbsp;napi_close_handle_scope(env,&nbsp;scope);
}


static&nbsp;napi_value&nbsp;PostAtFront(napi_env&nbsp;env,&nbsp;napi_callback_info&nbsp;info)&nbsp;{
&nbsp;&nbsp;size_t&nbsp;argc&nbsp;=&nbsp;1;
&nbsp;&nbsp;napi_value&nbsp;jsCb&nbsp;=&nbsp;nullptr;
&nbsp;&nbsp;napi_get_cb_info(env,&nbsp;info,&nbsp;&amp;argc,&nbsp;&amp;jsCb,&nbsp;nullptr,&nbsp;nullptr);
&nbsp;&nbsp;TaskRef*&nbsp;task_ref&nbsp;=&nbsp;new&nbsp;TaskRef();
&nbsp;&nbsp;napi_create_reference(env,&nbsp;jsCb,&nbsp;1,&nbsp;&amp;task_ref-&gt;ref);
&nbsp;&nbsp;static&nbsp;std::once_flag&nbsp;flag;
&nbsp;&nbsp;static&nbsp;CallbackData&nbsp;callbackData;
&nbsp;&nbsp;std::call_once(flag,&nbsp;[&amp;]()&nbsp;{
&nbsp;&nbsp;&nbsp;&nbsp;napi_value&nbsp;resourceName&nbsp;=&nbsp;nullptr;
&nbsp;&nbsp;&nbsp;&nbsp;napi_create_string_utf8(env,&nbsp;"NXTaskSchedule",&nbsp;NAPI_AUTO_LENGTH,&nbsp;&amp;resourceName);
&nbsp;&nbsp;&nbsp;&nbsp;napi_create_threadsafe_function(env,&nbsp;/*func=*/nullptr,&nbsp;/*async_resource=*/nullptr,&nbsp;/*async_resource_name=*/resourceName,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*max_queue_size=*/0,&nbsp;/*initial_thread_count=*/1,&nbsp;/*thread_finalize_data=*/nullptr,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*thread_finalize_cb=*/nullptr,
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;/*context=*/&amp;callbackData,&nbsp;/*call_js_cb=*/CallJs,&nbsp;/*result=*/&amp;callbackData.tsfn);
&nbsp;&nbsp;});
&nbsp;&nbsp;auto&nbsp;status&nbsp;=&nbsp;napi_call_threadsafe_function_with_priority(callbackData.tsfn,&nbsp;task_ref,&nbsp;napi_priority_high,&nbsp;false);
&nbsp;&nbsp;return&nbsp;nullptr;
}

第二步 内存泄漏对象排查:

      未能直接发现泄漏点,通过注释


更多关于HarmonyOS鸿蒙Next中ArkTS内存泄漏案例及常见问题的实战教程也可以访问 https://www.itying.com/category-93-b0.html

2 回复

在HarmonyOS Next中,ArkTS内存泄漏常见于未及时释放事件监听器、全局变量持有组件引用、以及异步任务未取消。例如,注册onPageShow事件后未在页面销毁时移除,或组件内使用AppStorage存储大量数据未清理,均会导致内存无法回收。其他问题包括循环引用、未关闭文件句柄或数据库连接。排查时需使用DevEco Studio的内存分析工具监控堆内存变化,定位未释放对象。

更多关于HarmonyOS鸿蒙Next中ArkTS内存泄漏案例及常见问题的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


感谢分享这份详尽的HarmonyOS Next中ArkTS内存泄漏案例分析与FAQ。内容涵盖了从SourceTextModule导致的无法释放GC root对象、应用侧业务对象未释放,到native接口内存泄漏的典型案例,并提供了snapshot分析、distance含义、OOM触发条件等关键知识点。

这些案例展示了内存泄漏的常见模式:export对象被系统持有需手动断开引用、事件监听回调函数不匹配导致无法解注册、napi接口未正确使用scope管理对象生命周期等。FAQ部分解答了OOM机制、snapshot解析、循环引用等常见疑问,对开发者理解和排查内存问题很有帮助。

这份资料为HarmonyOS Next开发者提供了实用的内存泄漏排查思路和解决方案参考。

回到顶部