Android C/C++ 内存泄漏分析 unreachable

背景

随着对客户端稳定性质量的不断深入,部分的重点、难点问题逐步治理,内存质量逐步成为了影响客户端质量的最突出的问题之一。因此淘宝对此进行了系统性的内存治理,成立了内存专项。

“工欲善其事、必先利其器”。本文主要讲述内存专项的工具之一,内存泄漏分析memunreachable

内存泄漏

内存泄漏(Memory Leak)是指程序中已动态分配的堆内存由于某种原因程序未释放或无法释放,造成系统内存的浪费,导致程序运行速度减慢甚至系统崩溃等严重后果。

对于 c/c++内存泄漏,由于存在指针要精确找到那些对象没有被引用是非常困难的,一直是困扰 c/c++重点、难点问题之一。目前也有一些基于类似 GC Swap-Mark 的算法去找到内存泄露,常见工具如 libmemunreachable,kmemleak,llvm leaksanitizer 这类工具也需要记录分配信息。

Android 的 libmemunreachable 是一个零开销的本地内存泄漏检测器。 它会使用不精确的“标记-清除”垃圾回收器遍历所有本机内存,同时将任何不可访问的块报告为泄漏。 有关使用说明,请参阅 libmemunacachable 文档[1]。虽然 Android 提供了 libmemunreachable 如此优秀的开源 c/c++内存泄漏工具,并内嵌到 Android 的系统环境,帮忙我们去定位内存泄漏问题,但是目前 libmemunreachable 使用依赖线下的 Debug 配置环境,无法支持淘宝 Release 包。

本文结合 libmemunreachable 源码,我们一起来欣赏 libmemunreachable 的实现原理以及淘宝对 libmemunreachable 改造用来实现对 Release 包的支持,帮助淘宝定位和排查线上的内存泄漏问题。

libmemunreachable 分析

基本原理

我们知道 JAVA GC 算法中,如果内存中的对象中,如果不在被 GcRoot 节点直接或间接持有,那么 GC 在适当的时间会触发垃圾回收机制,去释放内存。那么哪些节点可以被作为 GC 的 Root 节点:

  1. 虚拟机栈(栈帧中的本地变量表)中引用的对象;
  2. 方法区中的类静态属性引用的对象;
  3. 方法区中常量引用的对象;
  4. 本地方法栈中 JNI(即一般说的 Native 方法)中引用的对象。(JVM 中判断对象是否清理的一种方法是可达性算法.可达性算法就是采用 GC Roots 为根节点, 采用树状结构,向下搜索.如果对象直接到达 GC Roots ,中间没有任何中间节点.则判断对象可回收. 而堆区是 GC 的重点区域,所以堆区不能作为 GC roots。)

而 C/C++内存模型,堆 heap 、栈 stack、全局/静态存储区 (.bss 段和.data 段)、常量存储区 (.rodata 段) 、代码区 (.text 段)。libmemunreachable 通过 C/C++内存模型结合可达性算法,将栈 stack、全局/静态存储区 (.bss 段和.data 段)作为 GC Root 节点,判断堆 heap 中的内存是否被 GC Root 所持有,如果不被直接或间接持有,则被判定为泄漏(别较真,不一定要 100%的判断 C/C++的内存泄漏,而是可以分析可能存在的潜在泄漏)。

Android C/C++ 内存泄漏分析 unreachable_第1张图片

图 1 C/C++内存模型可达性算法示意图

libmemunreachable 会使用不精确的“标记-清除”垃圾回收器遍历所有本机内存,同时将任何不可访问的块报告为泄漏。

libmemunreachable 流程图

图 2 memunreachable 时序图

memunreachable 时序:

  • 创建 LeakPipe:用来与子进程通信,子进程发送数据,父进程接受数据;
  • Fork 子进程:通过 fork 子进程的方式来保护当前进程的状态;
  • CaptureThreads:通过 Ptrace 的方式使得目标进程可以被子进程 Dump,从而使得子进程获取父进程的信息;
  • CaptureThreadInfo:通过 PTRACE_GETREGSET 获取寄存器的信息,部分 Heap 的内存可能被寄存器持有,这些被寄存器持有的 Heap 不应该被判定为泄漏;
  • ProcessMappings:解析/proc/self/maps 文件信息,maps 文件记录了堆 heap 、栈 stack、全局/静态存储区 (.bss 段和.data 段)、常量存储区 (.rodata 段) 、代码区 (.text 段)等内存相关的信息;
  • ReleaseThreads:通过 Ptrace 的方式恢复目标进程的 Ptrace 状态,并且主进程结束等待,开始接受数据;
  • 第二次 Fork 子进程:这里又 Fork 一次子进程,我的理解可能是为了性能,第一次 Fork 的是收集了需要分析内存泄漏的相关信息,第二次 Fork 则在收集的相关信息基础上去分析;
  • CollectAllocations:从/proc/pid/maps 的信息中分类,将栈 stack、全局/静态存储区 (.bss 段和.data 段)放入 GC Root 节点,堆 heap 放入被检查的对象;
  • GetUnreachableMemory:获取不可达的泄漏内存,C/C++内存模型结合可达性算法开始工作,去分析可能泄漏的 Heaps;
  • PipeSend:通过 Pipe 将泄漏信息发送给主进程;
  • PipeReceiver:主进程接受泄漏数据。

核心代码如下:

//MemUnreachable.cpp
bool GetUnreachableMemory(UnreachableMemoryInfo &info, size_t lim

你可能感兴趣的:(android,c语言,c++,云计算,阿里云)