本系列文章旨在提供定位与解决OpenHarmony应用与子系统内存泄露的常见手段与思路,将会分成几个部分来讲解。首先我们需要掌握发现内存泄漏问题的工具与方法,以及判断是否可能存在泄漏。接着需要掌握定位泄漏问题的工具,以及抓取trace、分析trace,以确定是否有泄漏问题。如果发现问题的场景过于复杂,需要通过分解问题来简化场景。最后根据trace来找到问题代码并尝试解决。

目录:

  1. 发现问题

  2. 定位问题

  3. 分析Trace

  4. 分解问题

  5. 解决问题(Native)

  6. 解决问题(NAPI&JavaScript)

  7. 解决问题(综合)

内存查看工具

本教程提供如何发现应用或子系统是否有内存泄露问题的常见手段。内存泄露问题一般表现为进程的内存占用随着进程的持续运行而持续升高,且不会回落。那么想要确定是否有内存泄露,就需要知道在OpenHarmony上,有哪些工具可以查看进程的内存占用情况。

hidumper

通过hidumper --mem pid来查看进程的占用,如:

hidumper --mem 1234
hidumper --mem `pidof com.xxxx.xxxx`

结果如下:

我们重点关注如下几个指标:

ark js heap: JavaScript堆内存占用

native heap: Native堆内存占用

Total:总内存占用

smaps-show

通过hidumper --mem-smaps pid查看进程内存的详细信息,如:

hidumper --mem-smaps 1234
hidumper --mem-smaps `pidof com.xxxx.xxxx`

结果如下:

进程的详细占用,如so、资源、heap、binder、图形渲染相关等。我们关注Pss列即可,其中:

anon:ArkJS Heap:JavaScript堆内存

heap+anon:native_heap:Native堆内存

Summary:总内存占用

该工具作为hidumper的补充,在JavaScript与Native内存无明显变化,但总内存有变化时,可以起到较大的作用。

内存泄露脚本

内存泄露脚本的使用可以参考其他教程,该脚本可以周期性的抓取系统内进程的占用(通过hidumper --mem),并绘制曲线图,如下:

该工具适合长时间的观察进程的内存情况,由于缓存、内存回收等机制,导致有些情况下内存不是及时回收,需要长时间观察,因此该脚本是确认进程是否存在内存泄漏的重要工具

该工具需要安装python,下载地址:https://gitee.com/kunyuan-hongke/laval-tools/tree/master/stability_testing_tools/tools/memtest

其他诸如linux内核中的工具就不在此赘述。

内存泄露类型

hidumper与smaps-show查看内存占用时,需要重点关注JavaScript与Native的堆内存,原因就是OpenHarmony的内存泄露,分为Native泄露与JavaScript泄露。

内存泄露的根本原因则是由于代码的不合理,造成创建的对象无法被回收。因此需要了解不同类型的代码与不同类型的内存泄露之间的关系。我们首先来看看应用中有哪些代码及模块:

代码类型

上图从代码类型层面,描述了应用中的模块之间的关系。代码类型与内存的关系:

  • c/c++(Native)代码影响Native heap,其中NAPI(c++)代码能同时影响Native与JavaScript heap,这是由于NAPI能创建Native和JavaScript对象

  • JavaScript代码影响JavaScript heap,同时Native堆内存在一般情况下也会少量增长

泄露类型

Native泄露

Native泄露通常由应用框架、子系统、napi等部分的c/c++代码不合理造成的,表现为Native堆内存增长。

JavaScript泄露

JavaScript泄露通常由状态管理框架、应用JS代码、napi等部分的代码不合理造成的,表现为JavaScript堆内存增长。同时JavaScript对象的泄露,也会造成与其对应的Native对象无法释放,即Native堆内存在一般情况下也会增长

初步判断

  • 应用进程需要同时关注Native与JavaScript heap

  • 系统服务进程只需要关注Native heap

在整体内存占用有上涨趋势时:

  • 如果JavaScript内存占用没有明显增长、或增长后一段时间降下来,则是Native代码泄漏。

  • 如果JavaScript内存占用有增长,且Native内存占用增长不大,则是napi或JavaScript代码泄漏。

  • 如果JavaScript内存占用有增长,同时Native内存占用增长也比较大,则说明两种泄露都有。

通过不同类型的堆内存的增长情况,可以初步判断是什么类型的代码造成的泄露,这在团队协作时非常有用。如果判断是JavaScript代码则由应用或系统团队分析,具体是谁则需要进一步分析。如果是Native代码则由系统团队来分析。

测试工具

DevEco Testing、wukong系统遍历测试

DevEco Testing与wukong等工具的系统遍历测试功能,可以在不确定系统是否有内存泄露问题的情况下,随机遍历测试所有已安装的应用。再配合内存查看工具,如内存泄露脚本,在长时间压测后,观察各个进程的内存占用曲线。如果曲线有明显的上升趋势,则存在内存泄露的可能,如:

DevEco Testing单应用测试

如果确认某一应用存在泄露的可能,可以使用单应用测试功能,进行针对性的测试,以进一步确认是由于压力不够导致内存或缓存未回收导致的内存占用上涨,还是确实有泄露。

系统服务进程

需要注意的是,如果发现系统服务进程存在上升趋势,是没法直接针对其测试的,只能通过应用从侧面进行测试。因此这里就涉及到需要了解该进程提供什么服务,什么应用会调用该服务,如:

  • camera_host进程,其主要提供camera的开启、关闭、拍照、预览等能力。系统应用相机基本涵盖了camera_host提供的大部分能力,因此想要确定camera_host是否泄漏,就需要单独测试相机应用

  • foundation进程,该进程内运行了多个服务,如ams、bms、wms等,其中很多服务是应用运行所需的基础能力。因此很难找到一个单独的应用来涵盖foundation的大部分能力。此时只能通过遍历测试,加上长时间抓取foundation进程的trace,然后分析trace找出可疑的功能点,最后根据功能点来找合适的应用来测试,必要时,可以自行构造应用。

    抓取与分析trace请参考后续的教程。

通过应用单点测试

如果怀疑某个api有泄露,可以通过构造一个应用来单独测试该api,借助应用的setInterval函数或递归等方式来反复调用api。再通过内存查看工具查看应用或服务进程是否有增长。具体要查看哪个进程可以参考如下:

  • 如果api不是跨服务调用的,查看应用进程即可

  • 如果api是跨服务调用,但是client、proxy可能存在泄露,则查看应用进程

  • 如果api是跨服务调用,但是stub、service可能存在泄露,则查看系统服务进程

  • ipc是基于cs模型的跨进程通讯,分为了client、proxy、stub、service等层,其中client、proxy运行在客户端进程,stub、service运行在服务端进程

进一步确认

很多情况下,由于测试方法、资源访问、全局变量等原因,导致一两次操作后,内存上涨是很正常的现象,并不能证明有内存泄露。

Native heap

由于native对象是及时回收的,且缓存属于少数情况,因此测试时,可以先操作一两次内存上涨的场景后,再开始统计内存情况。后续如果每操作一次,内存占用成规律的上涨,则大概率存在泄露。

ArkJs heap

由于JavaScript对象不是及时回收的,因此在native的操作方法上,建议每次操作后,都手动触发gc,再统计内存情况。或者通过内存泄露脚本长时间的统计,占用成上升趋势且内存上涨超过30M还未回落的,基本就有泄露。如果长时间上涨仅有几M的,可以加长时间或加大压力或手动触发一次gc。

总结

想要初步确认是否有泄露,需要根据现有情况,选择不同的测试工具与内存查看工具,如:

  • 想要整机确认,选择系统遍历测试+内存泄露脚本

  • 针对单独的应用或服务,选择单应用测试+内存泄露脚本

  • 针对单独的应用页面或者系统API,选择setInterval函数或递归或手动等方式来反复调用

    • 涉及到js内存的,可以手动+hidumper+gc,或长时间测试+内存泄露脚本来确认

    • 仅涉及native内存的,可以手动+hidumper,或长时间测试+内存泄露脚本来确认

想要减少误判,需要尽量长时间的测试,加上内存泄露脚本生成的曲线来判断。

Logo

社区规范:仅讨论OpenHarmony相关问题。

更多推荐