问题背景和表现
后台程序长时间运行,内存占用很高,最终进程被系统OOM掉了。
[Wed Feb 16 00:20:37 2022] Memory cgroup out of memory: Killed process 54509
(live_main) total-vm:79190316kB, anon-rss:6376768kB, file-rss:5592kB, shmem-rss:0kB,
UID:0 pgtables:154232kB oom_score_adj:0
调查过程
此问题调查花费的时候比较长,总结起来原因有:
- 程序需要运行很久才会出现内存占用高,被OOM掉
- 非必现,线上出现概率较大,测试机器很难出现
- 业务层代码全部采用智能指针,内存申请在底层基础库代码,不应该有内存泄露的问题
- 对操作系统内存回收机制不了解
程序长时间运行内存越来越高,首先想到的是内存泄露。
需要在此声明:因为已知程序会频繁向操作系统申请和释放内存,所以会造成操作系统内存碎片的问题,也同时在思考内存碎片是否会导致内存(RSS)占用高而被操作系统kill掉。
调查过程总体分为两个步骤:
- 梳理出程序中所有申请内存的地方,逐一check,尽量避免内存申请。使用到了jemalloc
- 验证申请大量内存,然后释放掉,进程内存占用是否降低
jemalloc工具使用
jemalloc工具使用起来很简单,可参考链接
一般情况下,程序启动的时候都会初始化一些东西,从而占用一些内存,如果只想监控程序运行时的内存情况,则在完成初始化后通过设置prof.active为true即可。
bool active = true;
mallctl(prof.active , NULL, NULL, &active, sizeof(bool));
mallctl(prof.dump , NULL, NULL, NULL, 0);
通过jeprof生成的函数调用图可以很清楚的看到内存的申请情况,逐一修改,避免申请内存,同时也优化了程序性能。
突然想到需要做一个测试,申请大量内存,然后释放掉,看进程的内存占用情况。
猜测验证
#include <stdio.h>
#include <vector>
#include <malloc.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
void printMemory()
{
system("ps afux | egrep test_main");
system("free -h");
}
int main()
{
std::vector<char*> vv;
for (size_t i = 0; i < 1024*1024*10; i++)
{
char* ptr = new char[1024];
vv.push_back(ptr);
}
printMemory();
sleep(10);
for (auto &it : vv)
{
delete[] it;
}
printMemory(); // 第二次输出内存占用
sleep(1000);
return 0;
}
g++ -o test_main -g test_main.cpp
代码思路很简单,就是先申请10G的内存,然后在释放掉,但是进程不退出。
查看进程内存占用情况:进程仍然占用内存10G+
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
xx 2849290 29.3 68.9 11295116 11234800 pts/0 S+ 11:05 0:16 | \_ ./test_main
查看系统内存情况:系统已占用内存超过10G+
# free -h
total used free shared buff/cache available
Mem: 15G 12G 2.3G 162M 1.1G 3.0G
Swap: 0B 0B 0B
基本看到这个情况,本文调查的问题已经有了结论。
但是:为什么已经free掉的内存,进程的内存占用(RSS)还是没有降低呢,我的简单理解是:
free掉的内存没有立刻还给操作系统,操作系统后续会通过kswapd来回收,但是回收的时间不定
这个问题比较大,在此不做详细说明。详看链接
一个比较有趣的点是:修改每次申请内存的大小和总量,在不同机器测试,结果是不一样的。如果每次申请100MB,共申请10G,最终free掉之后,进程仅占用极少内存,free命令查看进程释放的10G内存已经还给了操作系统了
之后,打开两个控制台,先后运行测试程序,则会出现操作系统OOM掉程序的情况,详见:
[Fri Feb 18 15:58:44 2022] Out of memory: Kill process 1596380 (test_main) score 640 or sacrifice child
[Fri Feb 18 15:58:44 2022] Killed process 1596380 (test_main) total-vm:10793688kB, anon-rss:10731700kB, file-rss:1424kB, shmem-rss:0kB
[Fri Feb 18 15:58:44 2022] oom_reaper: reaped process 1596380 (test_main), now anon-rss:0kB, file-rss:0kB, shmem-rss:0kB
其中 anon-rss: 没有文件背景的页面,即匿名内存
总结
总体来说还是对操作系统内存模块理解不深,不清楚操作系统内存回收的机制。不清楚代码中调用了free和用linux命令free看到内存之间的关系,导致此问题调查时间比较长。