你经常会在基于Linux的系统上面临的问题之一是管理内存预算。如果程序使用的内存多于可用内存,则可能会发生交换,这常常会对性能造成严重影响,或者激活了内存溢出(OOM),从而完全杀死了进程。
在调整内存使用之前,通过配置优化或者负载管理,有助于了解给定程序实际使用多少内存。
如果你的系统本质上运行单用户程序(总是有很多系统进程),那么这很容易。例如,如果我在具有128GB RAM的系统上运行专用的MySQL服务器,则可以使用“ used”作为已使用内存的标识,并使用“ available”作为仍可以使用的内存。
root@rocky:/mnt/data2/mysql# free -h total used free shared buff/cache available Mem: 125Gi 88Gi 5.2Gi 2.0Mi 32Gi 36Gi Swap: 63Gi 33Mi 63Gi
对于交换(swap),如果系统交换不频繁,即使使用了交换空间,它通常也会保留“不需要的垃圾”,这不需要考虑计算。
如果你使用的是Percona的监控和管理工具(PMM),则会在“Memory Utilization”中看到它:
并在“node summary”面板的的“swap activity”图中:
如果运行的多个进程共享着资源,则事情会变得复杂,因为“used”内存和进程之间没有一一对应的映射。
让我们只列出其中的一些复杂性:
·fork进程时的copy-on-write语义:所有的进程共享“used”部分的内存,直到进程修改本身修改数据,这时候进程有自己的内存拷贝
·共享内存:顾名思义,共享内存就是不同进程间共享的内存
·共享库:库被映射到每个使用它的进程,是进程使用的内存的一部分,尽管在进程间是共享相同的库
·内存映射文件和匿名mmap():这里有很多更复杂的细节。例如,查看“内存映射文件”以获取更多详细信息。
考虑到这种复杂性,让我们看一下“top”输出,这是查看Linux当前负载的最常用程序之一。默认情况下,"top"按CPU使用率对进程进行排序,因此我们将按“ Shift-M”将其按(驻留)内存使用率进行排序。
首先你会注意到的是,该系统只有1GB的物理内存,并且具有多个进程,这些进程的虚拟内存(VIRT)超过1GB。
由于各种原因,现代内存分配器和编程语言(如GoLang)可以分配很多它们实际上没有使用的虚拟内存,因此虚拟内存的使用对于了解一个进程需要多少实际内存没有什么价值。
现在有常驻内存(RES),它向我们显示了该进程实际使用了多少物理内存。这很好...但是有问题。内存可以是非驻留内存,这是因为它不是真正的“used”并且仅作为虚拟内存存在,或者是因为它已被换出。
如果我们查看统计内核实际为该进程提供的内容,就会发现有更多可用数据:
root@PMM2Server:~# cat /proc/3767/status Name: prometheus Umask: 0022 State: S (sleeping) Tgid: 3767 Ngid: 0 Pid: 3767 PPid: 3698 TracerPid: 0 Uid: 1000 1000 1000 1000 Gid: 1000 1000 1000 1000 FDSize: 256 Groups: 1000 NStgid: 3767 17 NSpid: 3767 17 NSpgid: 3767 17 NSsid: 3698 1 VmPeak: 3111416 kB VmSize: 3111416 kB VmLck: 0 kB VmPin: 0 kB VmHWM: 608596 kB VmRSS: 291356 kB RssAnon: 287336 kB RssFile: 4020 kB RssShmem: 0 kB VmData: 1759440 kB VmStk: 132 kB VmExe: 26112 kB VmLib: 8 kB VmPTE: 3884 kB VmSwap: 743116 kB HugetlbPages: 0 kB CoreDumping: 0 Threads: 11 SigQ: 0/3695 SigPnd: 0000000000000000 ShdPnd: 0000000000000000 SigBlk: fffffffe3bfa3a00 SigIgn: 0000000000000000 SigCgt: fffffffe7fc1feff CapInh: 00000000a80425fb CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 00000000a80425fb CapAmb: 0000000000000000 NoNewPrivs: 0 Seccomp: 2 Speculation_Store_Bypass: vulnerable Cpus_allowed: 1 Cpus_allowed_list: 0 Mems_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000000,00000001 Mems_allowed_list: 0 voluntary_ctxt_switches: 346 nonvoluntary_ctxt_switches: 545
VmSwap是一个特别有趣的数据点,因为它显示了该进程使用的交换出的内存量。
VmRSS + VmSwap可以更好地指示进程所需的“物理”内存。在上面的例子中,它是1010MB,比常驻集284MB高很多,但比3038MB的“虚拟内存”大小小很多。
换出部分的问题,尽管我们不知道它是否被永久替换掉。例如,某些代码或数据在你的程序中没有用到,或者由于内存压力被换出。我们确实需要将其放在实际内存(RAM)中以实现最佳性能-但我们没有足够的可用内存。
在本例子中,要查看的有用的数据点是主要页面错误(major page faults)。它不在输出的内容中,但是可以从另一个文件中看:/proc/[pid]/stat。这里是stack overflow上的一些有用的信息(https://stackoverflow.com/questions/39066998/what-are-the-meaning-of-values-at-proc-pid-stat)。
主要页面错误(major page faults)数量多表示程序主要需要的不在物理内存中。这样就会包含交换活动,还包括对当前不在RAM中,共享库中当前未映射的代码的引用、或对内存映射文件中的数据的引用。无论如何,主要页面错误(major page faults)高发生率通常表明RAM压力大。
不过,让我们转到“top”的输出结果,看看是否可以在其中显示更多有用的信息。您可以使用“ F”键盘快捷键来选择要显示的字段。
你可以添加SWAP,Major Faults Delta和used列来显示我们在上面的谈论的所有内容!
观看这张图片,我们可以看到很大一部分“ prometheus”进程被换出了,并且每秒发生2K个主要页面错误。
“ clickhouse-serv”进程是另一个有趣的示例,因为它具有超过4G的“resident size”,但使用的内存相对较少,而主要页面错误也较少。
最后,让我们看一下“ percona-qan-api”进程,该进程只交换了很小的一部分,但也显示了2K的主要页面错误。老实说,我不太确定它是什么,但似乎与swap-IO没有关系。
最后
是否想查看进程正在使用多少内存? 不要查看虚拟内存大小或常驻内存大小,而是查看定义为常驻内存大小+交换使用情况的“used”内存。
想看看是否有实际的内存压力?针对你要调查的进程,查看系统级别(system-wide)的交换输入/输出统计信息以及主要页面错误(major page faults)。
原文:https://www.percona.com/blog/2020/09/11/how-much-memory-does-the-process-really-take-on-linux/