如何使用DevEco Studio性能调优工具Profiler定位应用内存问题

2025-01-16

鸿蒙应用开发过程中,可能由于种种原因导致应用内存未被正的使用或者归还至操作系统,从而引发内存异常占用、内存泄漏等问题,最终导致应用卡顿甚至崩溃,严重影响用户体验。

DevEco Profiler是集成在DevEco Studio中的一款原生鸿蒙应用性能优化工具,能够辅助开发者高效完成鸿蒙应用的性能问题定位与优化。在集成开发环境DevEcoStudio中可以以如下方式打开DevEco Profiler:

在DevEco Studio顶部菜单栏中选择“View-> Tool Windows -> Profiler”。

在DevEco Studio底部工具栏中单击“Profiler”。

按“Double Shift”或者“Ctrl+Shift+A”打开搜索功能,搜索“Profiler”。

DevEco Profiler中提供了针对鸿蒙应用内存问题的场景化分析模板SnapshotInsight与Allocation Insight,可以用于分析ArkTS以及Native内存,帮助开发者高效定位解决内存问题。下面将从识别问题、定界定位、优化验证三个方面来对DevEcoProfiler定位内存问题的方法进行介绍。

识别内存问题

1.1 监控应用内存

当应用的某项功能开发完成时,可以使用DevEco Profiler的实时监控功能对应用的各项资源进行监控,其中就包括应用内存资源。详细使用方法见性能问题定界。

性能问题定界:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/realtime-monitor-V13?catalogVersion=V13

实时监控界面所展示的是应用在运行过程中实际所使用的物理内存(ProportionalSet Size, PSS)、其他进程的物理内存占用以及操作系统的空闲内存,泳道的蓝色部分展示了当前进程的物理内存占用情况及趋势,左侧饼图则展示了当前时刻的瞬时内存使用数据。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第1张

此时,我们可以正常地操作应用,观察在当前功能运行过程中的应用内存变化情况,初步判断是否存在内存问题。当发现在一段时间内应用内存没有明显增加或者在内存上涨后又逐渐回落至正常水平,则基本可以排除应用存在内存问题;反之,如果应用内存占用明显与预期值不符合、在一段时间内不断上涨且无回落或者内存占用明显增长超出预期,那么则可初步判断应用可能存在内存问题,需要进一步分析。

1.2 初步定界内存问题

当从实时监控页面初步判断应用可能存在内存问题的时候,进一步地可以使用AllocationInsight或Snapshot Insight的Memory泳道来抓取应用内存在问题场景下的详细数据以及变化趋势,初步定界问题出现的位置(NativeHeap/ArkTS Heap/dev段等)。

在当前步骤下,录制内存数据时需要将Allocation Insight或SnapshotInsight中的其余泳道去除勾选,仅录制Memory泳道的数据(注:因为其余泳道会开启对内存分配、内存对象等数据的抓取,这些功能会带来额外的开销,可能会对我们初步定界问题产生噪音,影响分析,故先排除录制)。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第2张

在Memory泳道的录制过程中,不断操作应用在问题场景的功能,将问题放大,便于快速定界问题点,参考录制过程中的应用内存占用曲线,当曲线的上涨幅度达到一定大小时即可结束录制。

录制完成后,可以展开Memory泳道,查看应用内存分段的使用情况,也可以点击泳道上的options下拉框,自行选择想要关注变化情况的内存类型。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第3张

同时,可以选中Memory泳道或其任一子泳道(直接选中泳道时详情区域会展示完整的泳道数据)来查看在每个采样点的详细应用内存占用数据(注:详情区域数据采用PSS的维度衡量,数据近似于使用`hidumper --mem $pid`的第一列PSS值)。当鼠标左键单击选中表格中某一行的数据时,对应的泳道上将展示出当前的时间刻度线,方便快速定位内存变化的时间位置。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第4张

通过查看Memory的子泳道内存分类以及详情区域的内存详细占用,我们能够大致定界出有哪些位置的内存可能存在问题。例如上图中从Memory的子泳道数据图中可以看到,FilePageOther/Native Heap/ArkTS Heap均有较大的增长,因而可以以这三个方面的内存使用作为切入点,来进一步分析问题的根因。

定位内存问题

从1.2节中的分析可知,当前应用内存的增长主要集中在FilePage Other/NativeHeap/ArkTS Heap这三个部分,那么需要使用进一步的分析方法对这三个方面的内存进行分析,定位内存上涨的根因。

在分析鸿蒙应用的内存问题时,可以将鸿蒙应用的内存大体分为两部分,方舟虚拟机内存和Native内存:

1. 方舟虚拟机内存:由方舟虚拟机管控的应用内存,同其他的虚拟机内存(例如Java)管理策略相似,开发者可以使用并操控的内存基本集中于虚拟机堆上,在方舟虚拟机上被称作ArkTSHeap,这部分内存受到方舟虚拟机的管控。

2. Native内存:这部分内存主要是应用使用到的一些涉及Native的API所申请的内存以及开发者自己的Native代码所申请使用的堆内存(通常是C/C++),这部分内存需要开发者自己去管理申请和释放。

因为两种内存的使用方式和管理方式不尽相同,因此在对这两类内存的分析过程中所使用的方法也有比较大的区别,下面将从这两个方面分别介绍分析方法。

2.1 ArkTS内存问题

2.1.1 ArkTS内存管理

首先针对ArkTS Heap,由于该部分堆内存受到方舟虚拟机的管理,可以对堆内存进行垃圾回收(GarbageCollect,GC)和拍摄快照(HeapSnapshot或HeapDump,简称dump)以反映出瞬时的全量堆内存使用及分布情况,因此这部分内存的问题分析手段主要是对堆dump进行引用关系分析,分析泄漏对象无法被GC回收的原因。

不同于引用计数算法,方舟虚拟机采用可达性分析的机制来管理对象是否可被垃圾收集器回收,因此针对方舟虚拟机内存的分析方法主要集中于对象的引用分析,即分析哪些对象引用关系是错误的或者异常的,从而导致了泄漏对象被长时间持有无法被GC,最终通过解除强引用关系的手段来解决内存泄露问题。如下图1所示,蓝色的对象节点表示在内存引用分析中该对象GCRoot引用可达,其余对象GC Root引用不可达,引用不可达的对象在GC中可以被虚拟机回收。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第5张

图1 对象可达性分析

2.1.2 ArkTSHeap分析

针对虚拟机的内存问题分析通常都集中在对dump的对象分布及引用关系的分析上,方舟虚拟机也不例外,这里在DevEcoProfiler上提供了Snapshot Insight来对方舟虚拟机的堆内存进行分析。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第6张

在使用Snapshot分析时,通常会使用三快照技术(Three Snapshot Technique),通过内存快照的对比视图将某两次快照之间分配且仍然驻留的内存筛选出来,这些对象中的一部分就可能是导致内存泄漏的对象。通用的流程为:

打开应用,初始化场景(触发GC)-> 拍摄第一次Snapshot作为基准 -> 多(N)次触发内存泄漏操作 -> 拍摄第二次堆快照 -> 触发主动GC-> 拍摄第三次堆快照

由于方舟虚拟机提供了在获取堆快照之前自动GC的功能,因此我们可以将上述流程简化为两步,同时加上Profiler的录制功能,整体流程为:

打开应用,初始化场景-> 开启录制Snapshot Insight-> 拍摄第一次Snapshot作为基准 -> 多(N)次触发内存泄漏操作 -> 拍摄第二次堆快照-> 结束录制

录制完成后,会得到如下图所示的数据

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第7张

录制过程中,我们采集了两次堆快照,对应在Profiler的界面上就是两个紫色的条块,每一个条块内的数据都是当前的虚拟机堆快照。条块上的数字大小代表的是虚拟机堆内存的实际占用。

由于在每次拍摄堆快照之前,虚拟机都会触发GC,所以理论上堆快照内存在的对象都是当前虚拟机已经无法GC掉的对象,所以我们可以将两个堆快照进行比较,来查看哪些对象是我们在触发问题场景时新增了且不能释放的。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第8张

点击Snapshot Insight面板的Comparison页签,将两次Snapshot进行比较,如下图。图中数据的含义为以Snapshot2作为基准,Snapshot2对比Snapshot1的数据变化量。

即便是比较视图,东西也非常多,怎么分析呢?

还记得上面说的操作N次吗,在触发内存问题场景时将问题触发N次,在比较视图中首先就去找与N强相关、与业务代码强相关的constructor,首先来分析这些对象是否正常。

首先介绍一下Snapshot比较视图中各项数据的含义,如下图,更加具体的也可以参考内存泄露分析或者使用Snapshot Insight分析ArkTS内存问题。

内存泄漏分析:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/ide-insight-session-snapshot-V13?catalogVersion=V13

使用Snapshot Insight分析ArkTS内存问题:https://developer.huawei.com/consumer/cn/forum/topic/0207154775311720043?fid=0109140870620153026

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第9张

在找到相关的业务关联的对象后,可以从references里面一层层去寻找、排查在引用链上的可疑对象(一般指与业务代码关联的对象,尤其是xxx in com.xxx.yyy这种明显是业务使用到的对象的位置)。

在排查时,可以主要往两个方向:

1)Distance逐渐减小;

2)引用链上都是业务相关的对象。

从这两个维度去分析入度引用链,找出那些业务逻辑上应该释放但是实际并没有释放的对象及其引用关系,从这些引用关系上来排查是否有不合理的强引用导致的内存无法释放的问题,进而解决内存泄漏问题。

具体的案例可以参考:使用SnapshotInsight分析ArkTS内存问题

使用Snapshot Insight分析ArkTS内存问题:https://developer.huawei.com/consumer/cn/forum/topic/0207154775311720043?fid=0109140870620153026

2.2 分析Native内存

其次针对Native Heap,由于该部分堆内存仅由开发者自行控制分配释放,无法使用类似虚拟机的dump手段来分析整体的堆内存使用情况,仅可使用Profiler抓取到录制过程中的应用内存分配和释放事件,因此需要开发者通过内存分配/释放事件以及内存分配栈等信息自行找到代码中的内存申请和释放点来确认是否存在申请、释放不配对导致的内存泄漏问题。

内存分析模板Allocation提供了该能力,使用该模板可以抓取到经由系统基础库分配的Native部分内存,分析分配/释放的匹配逻辑,界面如下图。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第10张

在开始录制前,需要先了解一些该模板的工作原理以及相关的配置参数,以确定能够抓取到应用中可能存在的内存问题。

Allocation模板通过hook基础库中的某些函数调用来获取每次内存分配的数据,并将这些数据返回至Profiler中,在Profiler中完整的展示这些内存的分配、释放数据以及相关的调用栈、库等信息。详细的使用介绍可以参考这里内存分析及优化,下面就具体的使用流程做介绍。

内存分析及优化:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/ide-insight-session-allocations-memory-V13?catalogVersion=V13

2.2.1Native数据采集

首先,是录制前的参数配置,最新版本的配置页面如下,其中的配置项都是针对NativeAllocation这条泳道的,下面依次介绍:

1)Statistics Mode、Sampling Interval:该项配置代表是否开启统计模式采集数据,默认开启。开启后,数据会每隔SamplingInterval中设置的时间从设备端汇总并返回,该模式下工具的采集性能会更好、负载更低、可采集的时间也更长,但是会丢失掉精准的每次分配释放的内存信息;关闭该模式,会开启详情采集模式,返回的数据中会给出每次内存分配的详细情况,包含内存分配地址、分配大小、分配栈等信息。推荐使用统计模式,如业务侧有需要也可酌情使用详情模式。

2)Filter Size:过滤内存大小。该参数表示最小抓取的内存大小,默认为1024bytes(1kb),内存分配时小于该大小的内存分配信息不会被抓取到,应用可根据自身具体情况选择该参数。该参数可能会显著影响应用性能,当该值过小且应用在分配大量内存的场景时(抓取的内存分配数据量巨大)可能会造成应用卡顿,因此建议选择合适的参数。

3)Backtrace Mode:内存分配栈回栈方式,默认FP回栈。FP回栈性能更好,默认开启,但在某些特定场景下(例如so的编译参数控制),FP回栈可能失效,此时可选择DWARF回栈尝试。

4)Record JS Stack:是否开启JS回栈。当该开关打开时,系统回栈时会自动从Native向JS层回栈,完成Native到JS的栈缝合,适合ArkTS/JS代码调用Native的场景。

5)JS Backtrace Depth、Native Backtrace Depth:内存回栈深度。代表最大的回栈层数,层数越大对性能开销越大,可结合业务动态调整。

注:当选择了DWARF回栈后,JS和Native的回栈深度会变成一个框Backtrace Stack,其层数代表着JS与Native的共同回栈深度。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第11张

做好配置后,可以开始抓取问题场景的数据,切记需要在保存数据并开启录制之后,操作应用复现问题场景,并在问题复现完成后停止录制。(注:如果是使用统计模式,录制的结束时间需要是SamplingInterval即采集周期的整数倍,例如当采集周期是10s时,停止时间建议在11s+/21s+/31s+,以此类推,留出余量给系统做数据处理与传输)。录制完成后界面类似下图。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第12张

其中泳道部分展示了当前的内存变化情况,Native Allocation泳道下方又涉及两个子泳道AllHeap和All Anonymous VM,这两个泳道分别代表使用malloc和mmap函数分配的内存情况,下方的详情区域展示对应泳道的内存分配统计数据(Statistics页签)与内存分配栈(CallTrees)信息。

2.2.2Native数据分析

抓取到了问题场景的数据,接下来就是对问题数据的分析。

首先,框选范围内的数据展示的是:在框选范围的起点之后及在框选范围的终点之前的所有内存分配的数据,这个逻辑很重要,会对分析结论有很大影响,需重点关注。

接下来,在详情面板的左下角有一个下拉单选框,能够筛选当前详情区域展示的内存的数据,如下图所示:

1)All Allocations:框选的时间段的所有分配内存信息。

2)Created & Existing:在框选范围的起点之后分配的,且在框选范围的终点之前没有释放的内存数据。

3)Created & Released:在框选范围的起点之后分配的,且在框选范围的终点之前已经释放的内存数据。

这三个选项可以根据具体的业务问题和诉求来确定。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第13张

第二个框中的Native Size和Native Library代表可以通过这两个维度:大小和分配库的信息来做统计,一个是按照分配大小聚合、另一个是按照分配该内存的库来聚合。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第14张

通过这个页签的统计信息、对未释放的数量、大小等列排序后,能够大致分析出内存出现问题的情况以及场景,为分析内存栈做一些提前准备。接下来可以把数据切换到CallTrees内存分配栈上。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第15张

该部分数据展示了详细的内存分配栈信息,内存问题一般都是在该部分通过栈帧信息找出相关的业务逻辑来定位。其中有两个点需要说明:

1)栈帧中主要为Native栈,除了应用本身编译的一些so及带有部分接口信息的so信息外,其他系统库部分仅展示so库与函数偏移信息,若需要查看这部分信息,需要导入相应版本的带符号的so库。具体参考基础耗时分析中的离线符号解析小节。

基础耗时分析:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V13/ide-insight-session-time-V13?catalogVersion=V13

2)开启Record JS Stack之后,部分栈帧中可能包含JS栈信息,JS栈信息一般对应着应用源码,双击能够跳转到代码行上。

注:所有Category上有高亮色的栈帧信息,都可以通过双击跳转到源代码所在的文件和代码行上(前提是打开的工程是调优应用的相匹配的工程)。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第16张

同样地,在分析调用栈的过程中,可以使用下方操作栏上的一系列功能,除了上述介绍过的AllAllocations筛选能力之外,在Native Allocation泳道的Call Trees页签中,可以通过底部的“Call Trees”和“Constraints”选择框来过筛选和过滤内存分配栈。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第17张

Call Trees选择框包含两种过滤条件:

Separate byAllocated Size:在内存分配栈完全相同的情况下,会按照每次分配栈申请的内存大小将栈分开;

Hide SystemLibraries:隐藏内存分配栈中的系统堆栈。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第18张

Constraints选择框也包含了两种过滤条件:

Count:根据指定的内存申请次数过滤内存分配栈信息;

Bytes:根据指定的内存申请大小过滤内存分配栈信息。

业务方可以根据自身的业务情况与具体的问题场景来适当使用这些数据筛选能力,同时在下方也提供搜索功能,InvolvesSymbol Name搜索框提供了针对函数调用栈的文本搜索能力,可以快速搜索栈帧信息与so库信息并快捷跳转至对应位置。

另外,若表格的树状图不便于直观看出内存信息,可以使用Flame Chart火焰图开关,使用火焰图的形式来分析内存分配可能存在的问题,如下图,火焰图中的条块长度越长,代表该调用栈分配的内存大小越大。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第19张

2.2.3Native内存问题分析

在前面两个小节介绍了整个Native Allocation的使用及分析方式,这里简单介绍具体的问题分析逻辑:

1. 录制问题场景的内存分配信息;

2. 从统计信息及内存分配栈查看在当前范围内未释放的内存信息(选择Created &Existing),通过2.2.2节中介绍的各种分析和筛选能力找出未释放的内存堆栈,并将该堆栈结合业务逻辑分析;

3. 若涉及ArkTS代码对Native的调用逻辑,可开启Record JS Stack开关,将Native的内存栈回栈至JS层,简化分析难度;

4. 通过这些问题栈帧信息映射到业务代码中,结合问题场景和代码分析为何该部分内存未释放,找到问题点。

修改及验证

当经过上述步骤的流程分析完应用的内存问题之后,基本上已经可以定位到问题发生的位置及相关的代码段,在此基础上结合业务逻辑对代码做修改,修改后重新编译推包到真机上,在相同的场景下尝试复现问题,并使用实时监控或者Allocation/Snapshot模板的Memory泳道来监测应用内存占用情况,以确认问题是否还存在。

如何使用DevEco Studio性能调优工具Profiler定位应用内存问题 (https://ic.work/) 技术资料 第20张

总结

问题不是一朝一夕出现的,通常问题会在开发的过程中逐渐积累,到最终暴露出来时可能已经涉及了多个模块、多种逻辑,各种逻辑互相耦合,导致分析的难度大大增加。

这种情况下,我们建议把性能相关的工作也能做到平时,在开发过程中也去关心程序的性能问题。例如,刚写了一段很长的引用关系、增加了一些注册实例的逻辑或者做了一些组件间的变量传递,这种时候就可以去结合逻辑自己设想一下,会不会引发一定的性能问题,甚至可以在平时就用调优工具来检测一把。

这样做到每个开发阶段都保证了性能的可靠,那么在项目日益增大的同时,性能问题也不会严重到离谱、无法分析。

附录

DevEco Studio下载链接:https://developer.huawei.com/consumer/cn/deveco-studio/

DevEco Profiler官方指导文档:https://developer.huawei.com/consumer/cn/doc/harmonyos-guides-V5/ide-insight-V5

文章推荐

相关推荐