内存泄漏(增长)火焰图

本文介绍了四种追踪分析内存增长和泄漏的方法,包括追踪分配器函数、系统调用、和缺页中断。通过火焰图可视化,可以定位内存问题的代码路径。虽然分配器追踪开销大,但能直接发现问题;系统调用和映射追踪则相对低开销,而缺页中断追踪能显示占用物理内存的路径。这些方法适用于不同的场景,对于诊断内存问题非常有帮助。
摘要由CSDN通过智能技术生成

正文

当你的应用程序占用的内存不断地提升时,你不得不立即修复它。
造成这种情况的原因可能是因为错误配置而导致的内存增长,也可能是因为软件bug引起的内存泄露。
无论哪一种,由于垃圾回收机制开始积极响应(消耗CPU),一些应用的性能便会开始下降。
一旦某个应用增长得太过庞大,那么其性能会受调页机制(swapping)的影响出现断崖式下降,甚至可能直接被系统kill掉(Linux系统的OOM Killer)。
无论是内存泄漏还是内存增长,如果你的应用在扩展,你肯定想先看看其内部发生了什么,说不定其实是个很容易修复的小问题。但关键是你怎样才能做到呢?

在调试增长问题时,不管你是使用应用程序还是系统工具,通常都要检查一下应用配置以及内存使用情况。
内存泄漏问题往往更难处理,但好在有一些工具可以提供帮助。
一些工具采用对应用程序的malloc调用进行程序插桩(instrumentation)的方式,比如Valgrind memcheck,它还能够模仿一颗CPU,以至于所有的内存访问都能被检测到。

但使用该工具通常会导致某个应用程序变慢20-30倍,有时甚至会更慢;
另外一个工具是libtcmalloc的堆分析器,使用它能快一点,但应用程序还是会慢5倍以上;
还有一类工具(比如gdb工具)会引发core dump,并随后处理它来研究内存使用情况。
通常在发生core dump时会要求程序暂停,或者是终止,那么free()例程就会被调用。
所以尽管插桩型工具或者core dump技术都能够提供宝贵的细节,但别忘了你此时针对的是一个时刻增长的应用,这种情况下无论哪种工具都很难使用。

本文我会总结一下我在分析(运行时应用的)内存增长和内存泄漏问题时所用到的四种追踪方法。
运用这些方法能够得到有关内存使用情况的代码路径,随后我会使用栈追踪技术对代码路径进行检查,并且会以火焰图的形式把它们可视化输出。
我将在Linux上演示一下分析过程,随后概述一下其它系统的情况。

四种方法已在下图中用绿色字标记:
在这里插入图片描述
这些方法都有各自的缺陷,我将予以详细解释。目录:

  • | 先决条件 Linuxperf,eBPF |
  • | 1. 追踪分配器函数:malloc(),free(), … 2. brk() 3. mmap()4. 缺页中断 |
  • | 其他操作系统 |
  • | 总结 |

先决条件

下面所有方法都必须保证tracers能够正常地进行栈追踪,这可能需要你事先进行检查,因为栈追踪并不总是能够正常进行。
比如现在有很多应用程序在编译的时候都使用了 -fomit-frame-pointer 这个GCC选项,这会使得基于帧指针的栈追踪技术无法使用;
java等一些基于虚拟机的runtime则会进行即时编译,这样tracers(如果不提供额外信息)很可能找不到程序的符号信息,就会导致栈追踪的结果仅仅是一些16进制的数值。
还有其他一些陷阱,请参阅我以前基于perf写的栈追踪和JIT符号相关文章。

Linux: perf, eBPF

下面展示的方法是通用的,我将使用Linux作为目标示例,然后概括其他操作系统。

Linux平台上有很多可以用于分析内存的tracers,这里我选择了perf以及bcc/eBPF这两个标准的Linux tracers,它们都是Linux内核源码的一部分。
perf通常在较老的Linux版本上运行(也可在较新的Linux上运行),而eBPF则至少需要Linux 4.8才能进行栈追踪。
使用eBPF可以更轻松地概括内核情况,使得栈追踪更加高效并且降低了开销。

1. 追踪分配器函数: malloc(), free(), …

我们开始追踪malloc()free()等内存分配器函数。设想你用" -p PID"选项在某个进程上运行Valgrind memcheck工具,并收集60秒内其内存泄漏的信息。虽然不完整,但有这些信息已经有希望捕获严重的内存泄漏问题了。
即使针对单个进程,运行Valgrind memcheck工具会带来同样的性能下降,甚至比预期下降更多,但是这种损失只有在你需要追踪的时候才会出现,并且只持续很短的一段时间。

分配器函数在虚拟内存上运行,而不是物理(常驻)内存,通常后者才是泄漏检测的目标。不过幸运的是,通常在虚拟内存这一层进行分析,已经离目标(找出问题代码)非常接近了。

我有时也会追踪分配器函数,但是开销很大。
这使得追踪分配器函数更像是一种调试手段,而不是一种产品分析的方法。
开销很大是因为像malloc()free()这样的分配器函数在以很高的频率运行(每秒数百万次),哪怕每次只增加一点点开销,算下来整个开销也会增大非常多。
但为了解决既存问题,这些都是值得的,它的开销至少比使用Valgrind memchecktcmalloc的堆分析工具都要小一些。
内核工具eBPF4.9或更高版本的Linux上更加好用,

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值