正文
当你的应用程序占用的内存不断地提升时,你不得不立即修复它。
造成这种情况的原因可能是因为错误配置而导致的内存增长,也可能是因为软件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
上演示一下分析过程,随后概述一下其它系统的情况。
四种方法已在下图中用绿色字标记:
这些方法都有各自的缺陷,我将予以详细解释。目录:
- | 先决条件
Linux
:perf
,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 memcheck
或tcmalloc
的堆分析工具都要小一些。
内核工具eBPF
在4.9
或更高版本的Linux
上更加好用,