当进程通过malloc申请虚拟内存后,操作系统不会立即为其分配物理内存,而是在首次访问时,才触发缺页异常分配内存。对普通进程来说,能看到的是内核提供的虚拟内存,这些虚拟内存还需要通过页表,由系统映射为物理内存。
为平衡CPU与磁盘间的性能差异,Linux会使用Cache把文件数据缓存到内存中。
内存分配
用户空间内存包括多个不同类型的内存段,比如只读段、数据段、堆、栈以及文件映射段等,下面逐个分析哪些地方可能出现内存泄漏。
只读段,包括程序的代码和常量,只读段不会再去分配新的内存,因此不会产生内存泄漏。
数据段,包括全局变量和静态变量,这些变量在定义时就已经确定了大小,也不会产生内存泄漏。
栈内存由系统自动分配和管理,只会出现爆栈或者踩内存情况,超出了局部变量的作用域时,栈内存会被系统自动回收,不会出现内存泄漏情况。
堆内存由应用程序自己来分配和管理,如果业务没有正确释放堆内存,就会造成内存泄漏,所以没有特殊情况,尽量使用智能指针管理对象生命周期。
内存映射段,包括动态链接库和共享内存,其中共享内存由程序动态分配和管理。所以如果程序在分配后忘了回收,就会导致内存泄漏。
对于内存泄漏,只能依赖系统OOM机制杀死进程,但是此时已经造成了严重的性能问题,如系统的缓存频繁回收等,会导致业务不可用。
系统命令
top 能观察系统和进程的内存占用情况,vmstat能观察内存的变化趋势。
观察free列,如果一直在减小,说明系统可用内存在变少。可以通过top或ps来观察进程的内存使用情况,然后找出内存使用一直增长的进程,最后再通过pmap分析进程地址空间中内存的使用情况。
使用缓冲区分析工具cachetop分析这些缓存被哪些进程占用;free查看可用内存被哪些进程占用。
cachetop可以分析业务是否有绕过缓存直接读取磁盘,导致读写速度慢;strace可以查看具体的系统调用:
strace -p $(pgrep) PID
检测工具
如果是编码阶段,在上线前检查代码中资源获取的方式,尽量改为智能指针自动管理资源生命周期;禁止系统的swap机制,减少内存和硬盘的交换数据导致性能开销。
如果是调试阶段检查内存泄漏,可以mock掉业务的malloc和free,在malloc/new时将调用方法、申请地址写入指定文件新增一条记录,free时根据地址删除该条记录,在程序运行结束后可以从文件中找到内存泄漏的地方。
bcc中的memleak就是类似思路,利用插桩统计分配的字节和释放的字节,两者进行抵消,而没有释放的调用栈就是可能泄露的地方。
其他内存泄漏检测工具:
AddressSanitizer:速度快,首选;
Valgrind:拦截内存分配和释放函数,无需修改代码,但是速度比较慢;
mtrace:通过环境变量替换malloc和free函数,方便使用。