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

目录:

  1. 发现问题
  2. 定位问题
  3. 分析Trace
  4. 分解问题
  5. 解决问题(Native)
  6. 解决问题(NAPI&JavaScript)
  7. 解决问题(综合)

发现问题一文中,如果通过测试手段发现某进程可能存在泄露,接下就需要通过其他工具来查找泄露点,并通过分析代码来确认并解决问题。本篇教程主要讲解通过工具来定位问题-即定位泄漏点。

hiprofiler

hiprofiler用来抓取一段时间内,进程中创建对象的栈,包括Native对象与JavaScript对象。但是由于JavaScript对象均由libark_jsruntime.so统一创建与分配,创建栈无法区分对象是在哪里创建的,因此hiprofiler更多的是用来查看Native对象的创建栈。

录制

在设备shell中,通过如下命令录制制定进程的trace:

hiprofiler_cmd \
-c - \
-o /data/local/tmp/com.ohos.profilerdata.htrace \
-t 60 \
-s \
-k \
<<CONFIG
request_id: 1
session_config {
buffers {
pages: 16384
}
result_file: "/data/local/tmp/com.ohos.profilerdata.htrace" 
sample_duration: 60000
}
plugin_configs {
plugin_name: "nativehook" 
sample_interval: 2000
config_data {
process_name: "com.ohos.systemui"
save_file: false 
smb_pages: 16384
max_stack_depth: 20 
malloc_free_matching_interval: 2000 
malloc_free_matching_cnt: 2000
fp_unwind: false 
string_compressed: false 
fp_unwind: false 
blocked: true
}
}
CONFIG

其中:

  • -t 修改持续时间,单位秒,同时修改sample_duration,单位为毫秒
  • max_stack_depth修改抓取的创建栈的深度,会影响trace文件的大小
  • -o与result_file修改生成的文件路径
  • process_name为要抓取进程的进程名

文件生成后通过hdc file recv命令将其下载至pc:

hdc file recv /data/local/tmp/com.ohos.profilerdata.htrace D:\xxx.htrace

查看

需要借助DevEco Testing中的Trace解析工具来查看htrace文件:

img

点击Open trace file,选择录制的htrace文件后,显示如下:

img

接着展开Native Memory 1963,用鼠标滑选All Heap一行:

img

滑选All Heap后,就可以看到选中的时间段内创建的对象,也可以改变选择框大小,来查看不同时间段内的数据。界面下方4个标签页我们一般会用到Call Info与Native Memory两个。

Call Info

Call Info页中,以创建对象的线程的角度展示创建栈列表,包括已销毁的对象。选择一条记录后,在右侧会展示详细的创建栈,如下:

img

可以通过选择Created & Existing来筛选创建并还留存的对象,正常情况下我们不需要关注那些已经销毁的对象。

img

还能通过点击Size、%、Count等,将列表按对象占用大小、百分比、对象数量等排序。

Call Info中的列表不仅能纵向展示,还能横向展示。点击左下角图标就能切换显示模式:

img

横向展示,默认按每种对象栈创建对象的总占用大小排序,并且相同的创建栈会聚合到一起,方便分析。后续我们都将以这横向展示来分析。

Call Info中还能通过Sample Count Filter来筛选创建对象的次数,过滤不关心的数据:

img

也可以在Detail Filter中输入关键字过滤,如输入Ace,创建栈中所有含Ace的数据会被展示出来,其他则会被隐藏。

在横向展示模式中,占比较小的数据不太方便查看,我们可以鼠标单击该数据,进入详情:

img

在栈的最上方或栈的任意位置,点击鼠标右键则返回上一个页面,并将点击后的记录用蓝色标记出来:

img

Native Memory

Native Memory页中,以时间顺序展示了所有创建过的对象的信息,包括已销毁的对象。选择一条记录后,在右侧会展示详细的创建栈,如下:

img

点击标题可以改变排序,选择Created & Existing可以筛选创建并还留存的对象。

Smartperf Host

也可以通过搭建Smartperf Host服务,在网页上查看trace文件,使用方法与DevEco Testing一致。服务搭建参考:
https://gitee.com/openharmony/docs/blob/master/zh-cn/device-dev/device-test/smartperf-host.md

反编译

拿到对象的创建栈后,需要通过反编译来找到对应的代码的位置,这里使用addr2line工具。

带符号so

反编译需要带符号so,可以在如下地方找到:

自行编译:以RK3568为例,带符号so位于out/rk3568/lib.unstripped文件夹中

日构建:日构建的镜像,需要下载全量包才包含带符号so,同样位于lib.unstripped文件夹中

拿到带符号so后,通过如下命令查看对应源码行数:

addr2line -Cfie libxxxx.z.so 0x2659a

chrome devtools

hiprofiler可以查看Native对象的创建栈,却没法很好的查看JavaScript对象的创建栈。想要看JavaScript对象,需要借助PandaDebugger与chrome devtools。

通过chrome devtools dump应用js内存:

  1. 查看应用进程号:hdc shell pidof bundlename
  2. 开启应用debugger:hdc fport tcp:15035 localabstract:应用进程号PandaDebugger,15035端口号可修改
  3. 在chrome中打开:devtools://devtools/bundled/inspector.html?ws=//127.0.0.1:15035,15035端口号需修改为第二步中设置的号
  4. 选择Memory标签页
  1. 按上图选择后,点击start开始录制
  2. 应用操作结束后,点击停止录制,如下

    img

查看Snapshot

chrome生成的Snapshot,需要在时间轴上手动选择查看的时间段:

img

时间轴中,蓝色的竖线表示对象的创建,灰色的竖线表示对象的销毁。选择一段时间后,下方的列表中,默认展示的是创建并留存的对象。选择一个对象可以在下方的Allocation stack中看到该对象创建的函数。

Allocation stack中都是函数名,与类名一样的表示对象是在构造函数中创建。

对象占用大小

  • Shallow Size表示浅层大小,即对象本身占用的内存大小。
  • Retained Size表示保留大小,即对象及其所有属性等一起,占用的内存总大小。

总结

通过hiprofiler与chrome devtools,我们能抓到进程中的Native对象与JavaScript对象的创建栈,找到可疑的栈后,如重复多次创建的,使用addr2line工具反编译找到源码位置,再进一步分析。

Logo

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

更多推荐