你所在的位置: 首页 > 正文

Android Native 内存泄漏系统化解决方案

2019-08-16 点击:1288

简介:对于Android平台上的开发人员来说,C ++内存泄漏的分析和定位一直是个问题。由于地图渲染和导航等核心功能具有很高的性能要求,因此高科技地图应用程序中存在大量的C ++代码。解决这个问题尤为重要,对产品质量至关重要。 Gaode技术团队在实践中形成了一套解决方案。

分析和定位内存泄漏的核心是分配函数和堆栈回溯的统计信息。如果您只知道内存分配点不知道调用堆栈,则问题将变得非常复杂并增加成本。因此,两者都是不可或缺的。

1.堆栈回溯链接使用libunwind,消耗起来很昂贵。在更多本机代码的情况下,频繁的调用将导致应用程序卡住,并且监视所有内存操作功能的调用堆栈频率很高。调用与libunwind相关的函数。

2. ROM要求有限制,给日常开发和测试带来不便。

3.使用命令行或DDMS进行操作,每次需要准备环境时,手动操作,最终结果不够直观,缺乏对比分析。

因此,如何进行高效的堆栈回溯并构建系统的Android Native内存分析系统尤为重要。

Gaode地图基于这两点进行了一些改进和扩展。通过这些改进,可以通过自动化测试及时发现并解决这些问题,从而大大提高开发效率并降低故障排除成本。

在Android平台上,libunwind主要用于堆栈回溯,可以满足大多数情况。但是,libunwind实现中的全局锁定和展开表解析会导致性能下降。在多线程频繁调用的情况下,应用程序将更换卡并且无法使用。

加速原则

编译器的-finstrument-functions编译选项支持在函数的开头和结尾插入自定义函数,在每个函数的开头插入对__cyg_profile_func_enter的调用,并在结尾处插入对__cyg_profile_func_exit的调用。可以从这两个函数获得呼叫点地址,并且可以通过记录这些地址随时获得函数调用栈。

仪器后的效果示例:

2509688-2b916a8a9bc81004.png

这里需要注意的是,一些不需要检测的函数可以使用attribute((no_instrument_function))向编译器声明。

如何记录这些通话信息?我们希望实现此信息以在不同线程之间进行读取,并且不受影响。一种方法是使用线程同步机制,例如向读取和写入此变量添加临界区或互斥锁,但这会影响效率。

你能锁吗?此时,我想到了线程本地存储,简称为TLS。 TLS是一个专用存储区域,只能通过自己的线程访问,并且没有线程安全问题,这与此处的方案一致。

然后编译器用于记录调用堆栈并将其存储在线程本地存储中以实现堆栈回溯加速。具体实施如下:

1.使用编译器的-finstrument-functions编译选项在编译阶段插入相关代码。

2. TLS中呼叫地址的记录采用数组+光标的形式,实现最快的插入,删除和获取。

定义数组+游标的数据结构:

在TLS中初始化thread_stack_t的存储密钥:

将thread_stack_t初始化为TLS:

3.实现__cyg_profile_func_enter和__cyg_profile_func_exit以将呼叫地址记录到TLS。

__cyg_profile_func_enter的第二个参数call_site是调用点的代码段地址。当函数进入时,它将记录在已在TLS中分配的数组中。光标ptr-> current转移到左边,函数退出光标ptr->电流可以正确移动。

示意图:

2509688-5a6d29a97b4f576d.png

记录的方向和阵列的增长方向是不一致的,以便为获得堆栈提供更紧凑和有效的接口。存储器副本可以直接用于获得最近呼叫点的地址和最远呼叫点的地址的调用栈。

4.提供接口以获取堆栈信息。

5.将上述逻辑编译成动态库。其他业务模块依赖于动态库编译。同时,-finstrument-functions被添加到用于检测的已编译标志中,并且所有函数调用都记录在TLS中。用户可以在任何地方调用get_tls_backtrace(void ** backtrace,int max)来获取调用堆栈。

效果比较(使用Google的性能测试基准,手机型号:华为Imagination 5S,5.1系统):

Libunwind单线程

2509688-402de128e5834cc0.png

TLS模式单线程获取

2509688-25a078ab72c413a6.png

Libunwind 10个帖子

2509688-fe8b19595b1b58a7.png

TLS模式10个线程

2509688-dd8d21363b3bd2c0.png

从上面的几个图表中可以看出,这种模式比单线程模式下的libunwind堆栈快10倍,比10个线程中的libunwind堆栈快50-60倍。

优点和缺点

优点:速度大大提高,以满足更频繁的堆栈回溯的速度要求。

缺点:编译器仪表,体积变大,不能直接用作在线产品,仅用于内存测试包。这个问题可以通过持续集成来解决。每次项目不在库中时,C ++项目将生成一个公共库和一个相应的内存测试库。

经过上述步骤可以解决获取慢速内存分配堆栈的痛点,然后结合谷歌提供的工具,如DDMS,adb shell am dumpheap -n pid /data/local/tmp/heap.txt命令来实现Native内存泄漏问题。调查,但调查效率低,需要一定的手机环境。

因此,我们决定建立一个完整的系统系统,可以更方便地解决这些问题。以下是整体想法:

内存监控遵循LIBC的malloc_debug模块。不要用官方方式打开这个功能,比较麻烦,不利于自动化测试,你可以将一个副本编译成你自己的项目,挂钩所有内存函数,跳转到malloc_debug监控函数leak_xxx执行,这样malloc_debug监控所有内存应用/发布并进行相应的统计。

使用get_tls_backtrace实现malloc_debug模块中使用的LIBC_HIDDEN int32_t get_backtrace_external(uintptr_t * frames,size_t max_depth),与上述堆栈回溯加速方法结合使用。

建立Socket通信,支持外部程序通过Socket交换数据,以便更方便地访问内存数据。

?构建Web端,获取上传后可以解析的内存数据。这里,地址应该由addr2line反转。

编写一个与自动化测试相结合的测试用例。在测试开始时,通过Socket收集并存储存储器信息。在测试结束时,将信息上载到平台进行解析,并发送评估电子邮件。当遇到有问题的警报时,R&D同学可以通过内存曲线和Web端的调用堆栈信息直接检查问题。

系统性能示例:

2509688-a20b68d3e6639fb6.png

2509688-d7344091d8db4abc.png

2509688-d3ab66b337a04505.png

作者:德国高科技小弟弟

阅读原文

本文是云栖社区的原创内容,未经许可,不得转载。

日期归档
云顶国际线上娱乐 版权所有© www.020yechang.com 技术支持:云顶国际线上娱乐 | 网站地图