Linux可用内存充足时进程常驻内存被淘汰的问题

40 篇文章 1 订阅
31 篇文章 1 订阅

引子

最近遇到这样一个问题:生产环境的某个C++ GUI程序界面时常出现卡顿问题,经过排查与进程的大量IO有关,但是奇怪的是,即使IO已经结束,结束后操作界面时仍然会有卡顿问题。继续排查,发现进程常驻内存的代码段和数据段在大量IO之后变小了,排查过程在下面叙述。

问题排查

为了复现整个过程,使用以下demo代替GUI程序,能得到类似的效果:

#include <stdio.h>

const int ARRAY_SIZE = 200 * 1024 * 1024;
int array[ARRAY_SIZE] = {1};

int main()
{
    int total = 0;
    for (int i = 0; i < ARRAY_SIZE; i += 4096)
    {
        total += array[i];
    }
    printf("all data is loaded\n");
    getchar();
    return 0;
}

编译该demo,能够得到一个大小约为800M的可执行程序:

$ g++ main.cpp
$ ls -lh
total 801M
-rwxrwxr-x 1 imred imred 801M 79 23:09 a.out
-rw-rw-r-- 1 imred imred  276 79 23:09 main.cpp

运行该demo:

$ ./a.out
all data is loaded

待提示“all data is loaded”后使用top查看进程占用内存:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
10171 imred     20   0  823708 819948 819872 S   0.0 10.2   0:01.08 a.out

其中RES字段,即常驻内存,占用800M左右。
为了方便,我们使用另一个进程进行大量IO操作,也能复现这个问题:使用cat命令读取一个大小约为8G(我的内存总大小)的文件:

$ ls -lh
total 7.7G
-rw-r--r-- 1 imred imred 7.7G 79 23:45 bigfile.dat
$ cat bigfile.dat > /dev/null

待cat运行结束后,再次使用top查看进程内存占用情况:

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
10171 imred     20   0  823708    756    680 S   0.0  0.0   0:01.08 a.out

RES内存降到了几百KB。
重复上面的操作,但是这次我们不使用top比较IO前后内存占用情况,而是比较IO前后的/proc/[pid]/smaps文件,这个文件记录了进程内存布局,将IO前的该文件复制为smaps1.txt,IO后的该文件复制为smaps2.txt,比较这两个文件:

$ diff *.txt
47,48c47,48
< Rss:              819200 kB
< Pss:              819200 kB
---
> Rss:                   8 kB
> Pss:                   8 kB
51c51
< Private_Clean:    819192 kB
---
> Private_Clean:         0 kB
53c53
< Referenced:       819200 kB
---
> Referenced:            8 kB
62c62
< Locked:           819200 kB
---
> Locked:                8 kB

具体来看这部分有差异的内存段:
smaps1.txt:

560587c67000-5605b9c68000 rw-p 00001000 08:13 2621471                    /home/imred/Documents/Workspace/bigexe/a.out
Size:             819204 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:              819200 kB
Pss:              819200 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:    819192 kB
Private_Dirty:         8 kB
Referenced:       819200 kB
Anonymous:             8 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:           819200 kB

smaps2.txt:

560587c67000-5605b9c68000 rw-p 00001000 08:13 2621471                    /home/imred/Documents/Workspace/bigexe/a.out
Size:             819204 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                   8 kB
Pss:                   8 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:         0 kB
Private_Dirty:         8 kB
Referenced:            8 kB
Anonymous:             8 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                8 kB

这段内存区域驻留在内存中的大小从800M左右降到了只有几个KB,从其内存属性“rw-p”和大小,可以断定这个区域是demo的数据段。这将导致如果进程需要再次访问这部分数据,需要从磁盘中再次读取,这必然会影响性能。
在进行IO时,注意使用top观察系统可用内存大小,在我测试时,发现可用内存并没有明显下降,一直维持在50%以上。
因此问题现象总结下来就是:虽然可用内存非常充足,但是仍然出现了进程常驻内存被淘汰的情况,导致了程序卡顿。

原因分析

为什么我会认为可用内存是充足的呢?top命令的输出包含如下内容:

KiB Mem :  8041500 total,  3505324 free,  2518364 used,  2017812 buff/cache
KiB Swap:  2097148 total,  2097148 free,        0 used.  5369872 avail Mem 

如果是图形化的输出则是这样的:

KiB Mem : 33.6/8041500  [||||||||||||||||||||||||||||||||||                                                                  ]
KiB Swap:  0.0/2097148  [                                                                                                    ]

很明显,图形化输出显示当前有33.6%内存正在使用,对应到文字输出应该是used字段2518364 KiB,这些内存我认为应该是当前所有进程常驻内存与共享内存(只算一份)之和;剩下的66.4%的内存,对应到文字输出是avail Mem字段5369872 KiB,这些内存我认为是可以自由使用的,因此我认为内存是充足的。
然而事实是这样吗?下面来验证一下我的想法:
将所有脏磁盘缓存写回磁盘,清除磁盘缓存:

$ sync
$ echo 3 > /proc/sys/vm/drop_caches

记录当前内存使用情况:

KiB Mem :  8041500 total,  4133848 free,  2892348 used,  1015304 buff/cache
KiB Swap:  2097148 total,  2097148 free,        0 used.  5048732 avail Mem 

运行demo,直到输出“all data is loaded”,查看内存使用情况:

KiB Mem :  8041500 total,  3276864 free,  2898584 used,  1866052 buff/cache
KiB Swap:  2097148 total,  2097148 free,        0 used.  5013160 avail Mem 

  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND                                                                                                                                                                                         
10359 imred     20   0  823708 820032 819956 S   0.0 10.2   0:01.09 a.out               

发现了没有?used几乎没有增长,而buff/cache增长了800M左右,而demo的常驻内存(RES字段)恰好是800M左右,这说明什么问题呢?这说明了进程的常驻内存不见得都算在了used中,而是可能会算在buff/cache中。既然算在了buff/cache中,自然避免不了被淘汰的可能,当进行磁盘IO的时候,buff/cache会大量增长,增长的过程可能会导致demo进程算在buff/cache中的常驻内存被淘汰,也就出现了文章开头的问题:需要再次访问这些内存时,需要重新从磁盘中读取,导致卡顿。
那么问题来了,哪些内存会被算在buff/cache呢?我测试的结果是从文件中读取的、clean的内存段都会被算在buff/cache,如代码段、数据段和只读数据段,但是比较奇怪的是,其中只读数据段并没有出现被淘汰的情况,代码段和数据段都出现了被淘汰的情况,具体原因还不太清楚(这三种内存段的测试不是在同一个机器上做的,不保证在所有机器上结果都相同,也许和内核有关?),仍然需要进一步探究。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值