恼人的Linux内存统计

简介: 前几天遇到一个客户反馈的内存问题,大致背景是自建的数据库业务,购买的是阿里云裸金属服务器总是遇到内存不足的情况,每次重启过不了多久就会不断报内存页分配失败,而且无论怎么扩容内存业务改善都不大

   前几天遇到一个游戏业务TOP客户反馈的问题,大致背景是自建的数据库业务,购买的是阿里云裸金属服务器总是遇到内存不足的情况,每次重启过不了多久就会不断报内存页分配失败,而且无论怎么扩容内存业务改善都不大,内存问题我们遇到过很多类,今天就就着这个案例来说一说内存使用的场景分析


    问题排查前我们先大致整理一下Linux内存使用分为几个大类,以及都如何去统计,以便与日后排查问题能有更好的方向性。

一. 查看总的内存使用情况  

通常我们是这样看内存的剩余情况的:

[root@iZm5ed****sZ ~]# free -m 
             total       used       free     shared    buffers     cached
Mem:          7856       2332       5524          0          7       2123
-/+ buffers/cache:        200       7655
Swap:            0          0          0

关于这部分信息的解读我们结合《Linux Performance and Tuning Guidelines》中的截图:

image.png

截图取自(page 47):https://lenovopress.com/redp4285.pdf

从上图结合我们的测试环境可以获取到的信息如下:

1、总内存7856M ,已用内存2332M

2、由于buffers + cached内存实际上也是可用内存,该内存也可以通过echo 3 > /proc/sys/vm/drop_caches 回收pagechae、dentries and inodes ,所以实际上已经使用的内存是200M, 剩余free内存 7655M 。

PS:buffers + cached 分别是什么不展开讨论了上面链接的文档中也有详细说明,free命令的统计方式也在改进,后面版本已经不单独统计-/+ buffers/cache行了,直接帮我们算好了。那么真正used部分消耗在哪里了呢?


二. RSS 部分消耗

RSS(resident set size) 也就是每个进程用了具体的多少页的内存,linux内存管理中进程的代码,库,堆和栈都会消耗内存,但是申请出来的内存,只要没真正touch过,是不算的,这里怎么理解呢,例如我调用malloc申请一段内存,此时只在虚拟内存段中分配了,实际这一段空间并没有映射真正的物理内存,只有当你尝试写这个内存页时系统才会通过缺页异常为这个虚拟内存页映射物理内存。进程实际占用的物理内存我们只需要统计下RSS就可以了,我们可以从/proc/PID/status 里获取到每个进程的rss,其实ps,top等工具也是从/proc下面统计的:

[root@iZm5****rsZ ~]# cat /proc/1686/status 
Name: assist_daemon
State:  S (sleeping)
Tgid: 1686
Pid:  1686
PPid: 1
TracerPid:  0
Uid:  0 0 0 0
Gid:  0 0 0 0
Utrace: 0
FDSize: 256
Groups: 
VmPeak:    17968 kB
VmSize:    17828 kB
VmLck:         0 kB
VmHWM:      2192 kB
VmRSS:      2192 kB
VmData:    15064 kB
VmStk:        84 kB
VmExe:      2628 kB
VmLib:         0 kB
VmPTE:        52 kB
VmSwap:        0 kB
Threads:  8

同时为了统计方便/proc/PID/statm 第二列就是RSS内存使用page页的多少,而在linux下默认使用的page页大小是4KB 。所以我上面计算求和后,最后乘以4就是最终的内存大小,我们写个脚本遍历一下:

[root@iZm****rsZ ~]# cat test.sh 
#/bin/bash                                                                                                              
for PROC in `ls  /proc/|grep "^[0-9]"`
do
  if [ -f /proc/$PROC/statm ]; then
      TEP=`cat /proc/$PROC/statm | awk '{print ($2)}'`
      RSS=`expr $RSS + $TEP`
  fi
done
RSS=`expr $RSS \* 4`

三. slab内存消耗部分

slab分配器,是为了弥补buddy system分配器最小分配粒度是以物理页帧(page)为单位进行管理的缺陷,内核中有大量的数据结构只需要若干bytes的空间,倘若仍按页来分配,势必会造成大量的内存被浪费掉。那这部分怎么统计呢?我们可以先看下slabtop命令,可以理解为slab版本的top:

image.png

slab内存的消耗我们可以查看/proc/slabinfo文件,具脚本为:

[root@iZm5e****sZ ~]# echo `cat /proc/slabinfo |awk 'BEGIN{sum=0;}{sum=sum+$3*$4;}END{print sum/1024/1024}'` MB
88.8909 MB

四、PageTables内存消耗

通俗来讲,虚拟内存的管理的核心是解决如何在小的物理内存中运行更大程序的问题

在Linux中,解决这个问题的关键是一个叫做page table[PT页面转换表]的结构。Linux把物理内存分为了固定统一大小的块,称为page[页],一般为4KB,并且每个页都有一个编号 [page frame number]。这样一个512M大小的内存将包括128K个页。这种方式称为paging,使得操作系统对内存的管理更方便。page table的作用就是将进程操作的地址[虚拟地址]转换成物理地址。

那么去哪里看PageTables占用多少呢:

[root@iZm5****rsZ ~]# echo `grep PageTables /proc/meminfo | awk '{print $2}'` KB
2872 KB

把上面几个脚本合计一下:

[root@ali-test ~]# cat test.sh 
#/bin/bash
for PROC in `ls /proc/|grep "^[0-9]"`
do
  if [ -f /proc/$PROC/statm ]; then
      TEP=`cat /proc/$PROC/statm | awk '{print ($2)}'`
      RSS=`expr $RSS + $TEP`
  fi
done
RSS=`expr $RSS \* 4`
PageTable=`grep PageTables /proc/meminfo | awk '{print $2}'`
SlabInfo=`cat /proc/slabinfo |awk 'BEGIN{sum=0;}{sum=sum+$3*$4;}END{print sum/1024/1024}'`
echo $RSS"KB", $PageTable"KB", $SlabInfo"MB"
printf "rss+pagetable+slabinfo=%sMB\n" `echo $RSS/1024 + $PageTable/1024 + $SlabInfo|bc`
free -m
[root@ali-test ~]# sh test.sh 
310420KB, 5060KB, 93.2412MB
rss+pagetable+slabinfo=400.2412MB
              total        used        free      shared  buff/cache   available
Mem:           3789         193        2146           0        1449        3336
Swap:             0           0           0

这里我们会发现一个问题,我们计算的总内存占用400.2412M ,free 统计的used内存193M,首先这里free计算的used啥也不是,是直接减出来的:total - free - buffers - cache(这里buffer和cache都有一部分是不可回收的所以减多了具体可以看/proc/meminfo)

但是即使这样依然有差距,相差的到底是哪一部分?一般我们认为是shared内存重复统计导致的,我们在计算RSS时由于RSS包含了共享内存部分(RSS = total_RSS+files+share_mm),所以可能存在部分share内存被重复累加了,以sshd为例可以看到很多so等共享库包含在内:

[root@ali-test ~]# pmap  1046
1046:   /usr/sbin/sshd -D
0000565429212000    800K r-x-- sshd
00005654294d9000     16K r---- sshd
00005654294dd000      4K rw--- sshd
00005654294de000     36K rw---   [ anon ]
000056542b414000    132K rw---   [ anon ]
00007ff927ec6000     48K r-x-- libnss_files-2.17.so
00007ff927ed2000   2044K ----- libnss_files-2.17.so
00007ff9280d1000      4K r---- libnss_files-2.17.so
00007ff928b18000      4K r---- libkrb5support.so.0.1
00007ff928b19000      4K rw--- libkrb5support.so.0.1
00007ff928b1a000      8K r-x-- libfreebl3.so
00007ff928b1c000   2044K ----- libfreebl3.so
00007ff928d1b000      4K r---- libfreebl3.so
00007ff928d1c000      4K rw--- libfreebl3.so
00007ff928d1d000    232K r-x-- libnspr4.so
00007ff928d57000   2044K ----- libnspr4.so
00007ff928f56000      4K r---- libnspr4.so
00007ff928f57000      8K rw--- libnspr4.so
00007ff928f59000      8K rw---   [ anon ]
00007ff929d39000      4K rw---   [ anon ]
.
.
.
.
.
.
00007ff929d3a000    412K r-x-- libssl.so.1.0.2k
00007ff929da1000   2048K ----- libssl.so.1.0.2k
00007ff929fa1000     16K r---- libssl.so.1.0.2k
00007ff929fa5000     28K rw--- libssl.so.1.0.2k
00007ff929fac000    112K r-x-- libsasl2.so.3.0.0
00007ff929fc8000   2044K ----- libsasl2.so.3.0.0
00007ff92a1c7000      4K r---- libsasl2.so.3.0.0
00007ff92a1c8000      4K rw--- libsasl2.so.3.0.0
00007ff92a1c9000     92K r-x-- libpthread-2.17.so
00007ff92a1e0000   2044K ----- libpthread-2.17.so
00007ff92a3df000      4K r---- libpthread-2.17.so
00007ff92a3e0000      4K rw--- libpthread-2.17.so
00007ff92a3e1000     16K rw---   [ anon ]
00007ff92a3e5000     84K r-x-- libgcc_s-4.8.5-20150702.so.1
00007ff92a3fa000   2044K ----- libgcc_s-4.8.5-20150702.so.1
00007ff92a5f9000      4K r---- libgcc_s-4.8.5-20150702.so.1
00007ff92a5fa000      4K rw--- libgcc_s-4.8.5-20150702.so.1
00007ff92a5fb000    312K r-x-- libdw-0.176.so
00007ff92a649000   2048K ----- libdw-0.176.so
00007ff92a849000      8K r---- libdw-0.176.so
00007ff92a84b000      4K rw--- libdw-0.176.so
00007ff92a84c000     16K r-x-- libgpg-error.so.0.10.0
00007ff92a850000   2044K ----- libgpg-error.so.0.10.0
00007ff92aa4f000      4K r---- libgpg-error.so.0.10.0
00007ff92aa50000      4K rw--- libgpg-error.so.0.10.0
00007ff92aa51000    500K r-x-- libgcrypt.so.11.8.2
00007ff92aace000   2044K ----- libgcrypt.so.11.8.2
00007ff92accd000      4K r---- libgcrypt.so.11.8.2
00007ff92acce000     12K rw--- libgcrypt.so.11.8.2
00007ff92acd1000      4K rw---   [ anon ]
00007ff92acd2000     80K r-x-- liblz4.so.1.7.5
00007ff92ace6000   2044K ----- liblz4.so.1.7.5
00007ff92aee5000      4K r---- liblz4.so.1.7.5
00007ff92aee6000      4K rw--- liblz4.so.1.7.5
00007ff92aee7000    148K r-x-- liblzma.so.5.2.2
00007ff92be9c000      8K rw---   [ anon ]
00007ff92be9e000   1800K r-x-- libc-2.17.so
00007ff92c060000   2048K ----- libc-2.17.so
00007ff92c260000     16K r---- libc-2.17.so
00007ff92c264000      8K rw--- libc-2.17.so
00007ff92c266000     20K rw---   [ anon ]
00007ff92ebaa000    136K r-x-- ld-2.17.so
00007ff92edaa000     92K rw---   [ anon ]
00007ff92edca000      4K rw---   [ anon ]
00007ff92edcb000      4K r---- ld-2.17.so
00007ff92edcc000      4K rw--- ld-2.17.so
00007ff92edcd000      4K rw---   [ anon ]
00007fffd2868000    132K rw---   [ stack ]
00007fffd299b000      8K r-x--   [ anon ]
ffffffffff600000      4K r-x--   [ anon ]
 total           112880K
[root@ali-test ~]# cat /proc/1046/statm 
28219 1091 833 200 0 190 0

上面有两个结论:

1. statm中第二段是RSS = 1091 pages ,第三段是share = 833 pages,RSS包含了share,但是和pmap中计算的total为什么差这么多呢,因为total里把每个so的所有内存都算上了,而进程真正占用的部分是遵从最小化原则的就是调用了的部分才会映射进来,所以实际以statm里的第三段为准。

2. 从用户态很难严格区分每个共享内存被几个进程占用,从而去抵消重复计算部分,所以共享内存存在计算误差。

    结合上面的内容,我们介入后发现了几个特征,其一从meminfo看业务报错时其实free内存还有好几个G,其二系统日志中在不断打印页申请失败(page allocateion failure)如下:

网络异常,图片无法展示
|

从这里可以看出,客户环境有大量的4K free页,但是4K以上的页已经全为0了,很明显齐内存碎片化非常严重,此时申请大页内存基本都会失败,说明客户业务在频繁申请释放4K页打散了大页内存。同时可以看到pagetable快接近150G了,说明虚拟内存页表非常大明显是不正常或者说不健康的。


1. echo 1 >/proc/sys/vm/compact_memory (手动触发一次内存规整,会合并部分可移动内存页,是有损的因为要遍历内存所以一般线上环境不推荐)

2. /proc/sys/vm/extfrag_threshold 调整这个默认值,默认是500,可以尝试调整到200,表示系统在内存碎片的处理倾向于做memory compaction,迁移合并及时的话,就可以缓解碎片,这个最好是在开机后默认配置,不要等出问题后再配。

3. sync; echo 3 >/proc/sys/vm/drop_caches 主动回收cache,只是缓解一下不解决问题。

4.  vm.zone_reclaim_mode , 我们线上的主机中该参数均为默认值 0, 不会触发 reclaim 操作而是直接返回 zone full ,所以改为1,但是如果做为文件服务器的话建议改为0,因为这种场景大部分内存应该都需要用于文件系统缓存来提高IO响应时间。

客户业务主要为自建oracle数据库,且没有启用hugepage,大内存部署oracle还是推荐使用大页,这是最大的一个问题:

1. 配置数据库使用大页,配置sga_max_size

2. 设置系统预留大页内存:vm.nr_hugepages=xxx(hugepage是2M,pages换算的总大小要大于数据库定义的sga_max_size)

3. 配置memlock数量要大于大页的数量 -1代表不限制:/etc/security/limits.conf

oracle   soft   memlock    -1

oracle   hard   memlock    -1


目录
相关文章
|
2月前
|
安全 Linux Shell
Linux上执行内存中的脚本和程序
【9月更文挑战第3天】在 Linux 系统中,可以通过多种方式执行内存中的脚本和程序:一是使用 `eval` 命令直接执行内存中的脚本内容;二是利用管道将脚本内容传递给 `bash` 解释器执行;三是将编译好的程序复制到 `/dev/shm` 并执行。这些方法虽便捷,但也需谨慎操作以避免安全风险。
179 6
|
12天前
|
算法 Linux 开发者
深入探究Linux内核中的内存管理机制
本文旨在对Linux操作系统的内存管理机制进行深入分析,探讨其如何通过高效的内存分配和回收策略来优化系统性能。文章将详细介绍Linux内核中内存管理的关键技术点,包括物理内存与虚拟内存的映射、页面置换算法、以及内存碎片的处理方法等。通过对这些技术点的解析,本文旨在为读者提供一个清晰的Linux内存管理框架,帮助理解其在现代计算环境中的重要性和应用。
|
18天前
|
存储 缓存 监控
|
1月前
|
存储 缓存 监控
Linux中内存和性能问题
【10月更文挑战第5天】
38 4
|
1月前
|
算法 Linux
Linux中内存问题
【10月更文挑战第6天】
41 2
|
15天前
|
缓存 算法 Linux
Linux内核中的内存管理机制深度剖析####
【10月更文挑战第28天】 本文深入探讨了Linux操作系统的心脏——内核,聚焦其内存管理机制的奥秘。不同于传统摘要的概述方式,本文将以一次虚拟的内存分配请求为引子,逐步揭开Linux如何高效、安全地管理着从微小嵌入式设备到庞大数据中心数以千计程序的内存需求。通过这段旅程,读者将直观感受到Linux内存管理的精妙设计与强大能力,以及它是如何在复杂多变的环境中保持系统稳定与性能优化的。 ####
24 0
|
1月前
|
存储 缓存 固态存储
|
2月前
|
Linux Shell
10-9|linux上统计文件中单词次数
10-9|linux上统计文件中单词次数
|
1月前
|
Linux C++
Linux c/c++文件虚拟内存映射
这篇文章介绍了在Linux环境下,如何使用虚拟内存映射技术来提高文件读写的速度,并通过C/C++代码示例展示了文件映射的整个流程。
46 0
|
3月前
|
机器学习/深度学习 消息中间件 Unix
深入理解Linux虚拟内存管理(九)(下)
深入理解Linux虚拟内存管理(九)
37 1