Linux内存管理中的RSS和VSZ是什么?在多线程环境中,如何管理和跟踪这两者?
RSS 是驻留集大小,用于显示为该进程分配了多少内存并且在 RAM 中。它不包括被换出的内存。只要这些库中的页面实际上在内存中,它就包含来自共享库的内存。它确实包括所有堆栈和堆内存。
VSZ 是虚拟内存大小。它包括进程可以访问的所有内存,包括已换出的内存、已分配但未使用的内存以及来自共享库的内存。
因此,如果进程 A 有 500K 的二进制文件并链接到 2500K 的共享库,则有 200K 的堆栈/堆分配,其中 100K 实际上在内存中(其余的被交换或未使用),它实际上只加载了 1000K 的共享库然后是 400K 自己的二进制文件:
RSS: 400K + 1000K + 100K = 1500K
VSZ: 500K + 2500K + 200K = 3200K
由于部分内存是共享的,因此许多进程可能会使用它,因此如果将所有 RSS 值相加,您很容易得到比系统更多的空间。
在程序实际使用之前,分配的内存也可能不在 RSS 中。因此,如果您的程序预先分配了一堆内存,然后随着时间的推移使用它,您会看到 RSS 上升而 VSZ 保持不变。
还有PSS(proportional set size)。这是一种较新的度量,它跟踪共享内存作为当前进程使用的比例。因此,如果之前有两个进程使用相同的共享库:
PSS: 400K + (1000K/2) + 100K = 400K + 500K + 100K = 1000K
线程都共享相同的地址空间,因此每个线程的 RSS、VSZ 和 PSS 与进程中的所有其他线程相同。使用 ps 或 top 在 linux/unix 中查看此信息。
还有比这更多的方法,要了解更多信息,请查看以下参考资料:
http://manpages.ubuntu.com/manpages/en/man1/ps.1.html
https://web.archive.org/web/20120520221529/http://emilics.com/blog/article/mconsumption.html
另见:
一种确定进程“真实”内存使用情况的方法,即私有脏 RSS?
RSS 是 Resident Set Size(物理驻留内存——当前占用机器物理内存中的空间),VSZ 是虚拟内存大小(分配的地址空间——在进程的内存映射中分配了地址,但不一定有现在一切都在它背后的实际记忆)。
请注意,在当今常见的虚拟机中,从机器的角度来看,物理内存可能并不是真正的物理内存。
最小可运行示例
为此,您必须了解分页的基础知识:How does x86 paging work?,尤其是操作系统可以通过页表/其内部内存簿记(VSZ 虚拟内存)分配虚拟内存,然后它实际上具有后备存储RAM 或磁盘(RSS 常驻内存)。
现在为了观察这一点,让我们创建一个程序:
使用 mmap 分配比我们的物理内存更多的 RAM
在每一页上写入一个字节,以确保每一页从仅虚拟内存 (VSZ) 变为实际使用的内存 (RSS)
使用以下方法之一检查进程的内存使用情况:C 中当前进程的内存使用情况
主程序
#define _GNU_SOURCE
#include <assert.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <unistd.h>
typedef struct {
unsigned long size,resident,share,text,lib,data,dt;
} ProcStatm;
/* https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c/7212248#7212248 */
void ProcStat_init(ProcStatm *result) {
const char* statm_path = "/proc/self/statm";
FILE *f = fopen(statm_path, "r");
if(!f) {
perror(statm_path);
abort();
}
if(7 != fscanf(
f,
"%lu %lu %lu %lu %lu %lu %lu",
&(result->size),
&(result->resident),
&(result->share),
&(result->text),
&(result->lib),
&(result->data),
&(result->dt)
)) {
perror(statm_path);
abort();
}
fclose(f);
}
int main(int argc, char **argv) {
ProcStatm proc_statm;
char *base, *p;
char system_cmd[1024];
long page_size;
size_t i, nbytes, print_interval, bytes_since_last_print;
int snprintf_return;
/* Decide how many ints to allocate. */
if (argc < 2) {
nbytes = 0x10000;
} else {
nbytes = strtoull(argv[1], NULL, 0);
}
if (argc < 3) {
print_interval = 0x1000;
} else {
print_interval = strtoull(argv[2], NULL, 0);
}
page_size = sysconf(_SC_PAGESIZE);
/* Allocate the memory. */
base = mmap(
NULL,
nbytes,
PROT_READ | PROT_WRITE,
MAP_SHARED | MAP_ANONYMOUS,
-1,
0
);
if (base == MAP_FAILED) {
perror("mmap");
exit(EXIT_FAILURE);
}
/* Write to all the allocated pages. */
i = 0;
p = base;
bytes_since_last_print = 0;
/* Produce the ps command that lists only our VSZ and RSS. */
snprintf_return = snprintf(
system_cmd,
sizeof(system_cmd),
"ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == \"%ju\") print}'",
(uintmax_t)getpid()
);
assert(snprintf_return >= 0);
assert((size_t)snprintf_return < sizeof(system_cmd));
bytes_since_last_print = print_interval;
do {
/* Modify a byte in the page. */
*p = i;
p += page_size;
bytes_since_last_print += page_size;
/* Print process memory usage every print_interval bytes.
* We count memory using a few techniques from:
* https://stackoverflow.com/questions/1558402/memory-usage-of-current-process-in-c */
if (bytes_since_last_print > print_interval) {
bytes_since_last_print -= print_interval;
printf("extra_memory_committed %lu KiB\n", (i * page_size) / 1024);
ProcStat_init(&proc_statm);
/* Check /proc/self/statm */
printf(
"/proc/self/statm size resident %lu %lu KiB\n",
(proc_statm.size * page_size) / 1024,
(proc_statm.resident * page_size) / 1024
);
/* Check ps. */
puts(system_cmd);
system(system_cmd);
puts("");
}
i++;
} while (p < base + nbytes);
/* Cleanup. */
munmap(base, nbytes);
return EXIT_SUCCESS;
}
编译并运行:
gcc -ggdb3 -O0 -std=c99 -Wall -Wextra -pedantic -o main.out main.c
echo 1 | sudo tee /proc/sys/vm/overcommit_memory
sudo dmesg -c
./main.out 0x1000000000 0x200000000
echo $?
sudo dmesg
在哪里:
0x1000000000 == 64GiB:2x 我电脑的物理 RAM 为 32GiB
0x200000000 == 8GiB:每 8GiB 打印一次内存,所以在崩溃前我们应该在 32GiB 左右打印 4 次
回声 1 | sudo tee /proc/sys/vm/overcommit_memory:Linux 需要允许我们进行大于物理 RAM 的 mmap 调用:malloc 可以分配的最大内存
程序输出:
extra_memory_committed 0 KiB
/proc/self/statm size resident 67111332 768 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 1648
extra_memory_committed 8388608 KiB
/proc/self/statm size resident 67111332 8390244 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 8390256
extra_memory_committed 16777216 KiB
/proc/self/statm size resident 67111332 16778852 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 16778864
extra_memory_committed 25165824 KiB
/proc/self/statm size resident 67111332 25167460 KiB
ps -o pid,vsz,rss | awk '{if (NR == 1 || $1 == "29827") print}'
PID VSZ RSS
29827 67111332 25167472
Killed
退出状态:
137
128 + signal number rule 表示我们得到信号编号 9
,man 7 signal
表示是 SIGKILL,它是由 Linux out-of-memory killer 发送的。
输出解释:
在 mmap 之后,VSZ 虚拟内存在 printf '0x%X\n' 0x40009A4 KiB ~= 64GiB (ps 值以 KiB 为单位)保持不变。
RSS“实际内存使用”仅在我们触摸页面时才会延迟增加。例如:在第一次打印时,extra_memory_committed 为 0,这意味着我们还没有触及任何页面。 RSS 是一个小的 1648 KiB,已分配给正常程序启动,如文本区域、全局变量等。在第二次打印时,我们已写入 8388608 KiB == 8GiB 的页面。结果,RSS 恰好增加了 8GIB 至 8390256 KiB == 8388608 KiB + 1648 KiB RSS 继续以 8GiB 的增量增加。最后一次打印显示大约 24 GiB 的内存,在打印 32 GiB 之前,OOM 杀手杀死了进程
在第一次打印时,extra_memory_committed 为 0,这意味着我们还没有触及任何页面。 RSS 是一个 1648 KiB 的小空间,已分配给正常的程序启动,如文本区域、全局变量等。
在第二次打印中,我们已经写入了 8388608 KiB == 8GiB 的页面。结果,RSS 增加了 8GIB 到 8390256 KiB == 8388608 KiB + 1648 KiB
RSS 继续以 8GiB 为增量增加。最后一次打印显示大约 24 GiB 的内存,在打印 32 GiB 之前,OOM 杀手杀死了进程
另请参阅:https://unix.stackexchange.com/questions/35129/need-explanation-on-resident-set-size-virtual-size
OOM 杀手日志
我们的 dmesg
命令显示了 OOM 杀手日志。
已在以下位置询问了对这些内容的确切解释:
了解 Linux oom-killer 的日志,但让我们在这里快速浏览一下。
https://serverfault.com/questions/548736/how-to-read-oom-killer-syslog-messages
日志的第一行是:
[ 7283.479087] mongod invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
所以我们看到有趣的是,总是在我的笔记本电脑的后台运行的 MongoDB 守护进程首先触发了 OOM 杀手,大概是当可怜的东西试图分配一些内存时。
但是,OOM 杀手并不一定会杀死唤醒它的人。
调用后,内核会打印一个包含 oom_score
的表或进程:
[ 7283.479292] [ pid ] uid tgid total_vm rss pgtables_bytes swapents oom_score_adj name
[ 7283.479303] [ 496] 0 496 16126 6 172032 484 0 systemd-journal
[ 7283.479306] [ 505] 0 505 1309 0 45056 52 0 blkmapd
[ 7283.479309] [ 513] 0 513 19757 0 57344 55 0 lvmetad
[ 7283.479312] [ 516] 0 516 4681 1 61440 444 -1000 systemd-udevd
更进一步,我们看到我们自己的小 main.out
实际上在之前的调用中被杀死:
[ 7283.479871] Out of memory: Kill process 15665 (main.out) score 865 or sacrifice child
[ 7283.479879] Killed process 15665 (main.out) total-vm:67111332kB, anon-rss:92kB, file-rss:4kB, shmem-rss:30080832kB
[ 7283.479951] oom_reaper: reaped process 15665 (main.out), now anon-rss:0kB, file-rss:0kB, shmem-rss:30080832kB
该日志提到了该进程具有的 score 865
,大概是最高(最差)OOM 杀手分数,如下所述:https://unix.stackexchange.com/questions/153585/how-does-the-oom-killer-decide-which-process-to-kill-first
同样有趣的是,显然一切都发生得如此之快,以至于在计算释放的内存之前,oom
再次被 DeadlineMonitor
进程唤醒:
[ 7283.481043] DeadlineMonitor invoked oom-killer: gfp_mask=0x6200ca(GFP_HIGHUSER_MOVABLE), order=0, oom_score_adj=0
这一次杀死了一些 Chromium 进程,这通常是我的计算机正常的内存猪:
[ 7283.481773] Out of memory: Kill process 11786 (chromium-browse) score 306 or sacrifice child
[ 7283.481833] Killed process 11786 (chromium-browse) total-vm:1813576kB, anon-rss:208804kB, file-rss:0kB, shmem-rss:8380kB
[ 7283.497847] oom_reaper: reaped process 11786 (chromium-browse), now anon-rss:0kB, file-rss:0kB, shmem-rss:8044kB
在 Ubuntu 19.04、Linux 内核 5.0.0 中测试。
Linux 内核文档
https://github.com/torvalds/linux/blob/v5.17/Documentation/filesystems/proc.rst 有几点。那里没有使用“VSZ”一词,但使用了“RSS”,没有什么太有启发性的了(惊喜?!)
代替 VSZ,内核似乎使用术语 VmSize
,例如出现在 /proc/$PID/status
上。
一些感兴趣的报价:
这些行中的第一行显示的信息与 /proc/PID/maps 中的映射显示的信息相同。以下几行显示了映射的大小(大小);支持 VMA 时分配的每个页面的大小(KernelPageSize),通常与页表条目中的大小相同; MMU 在支持 VMA 时使用的页面大小(在大多数情况下,与 KernelPageSize 相同);当前驻留在 RAM (RSS) 中的映射量;进程在此映射中的比例份额 (PSS);以及映射中干净和脏共享和私有页面的数量。进程的“比例集大小”(PSS)是它在内存中的页数,其中每个页除以共享它的进程数。因此,如果一个进程自己拥有 1000 个页面,并与另一个进程共享 1000 个页面,则其 PSS 将为 1500。请注意,即使是 MAP_SHARED 映射的一部分,但只有一个 pte 映射的页面,即当前使用只有一个进程,被视为私有而不是共享。
所以我们可以猜测更多的事情:
单个进程使用的共享库出现在 RSS 中,如果多个进程拥有它们,则不会
jmh 提到了 PSS,并且在“我是唯一持有共享库的进程”和“有 N 个进程持有共享库,所以每个进程平均持有内存/N”之间有一个更成比例的方法
VSZ - 虚拟集大小
虚拟集大小是在初始执行期间分配给进程(程序)的内存大小。虚拟集大小内存只是一个进程可用于其执行的内存数量。
RSS - 驻留集大小(有点 RAM)
与 VSZ(虚拟集大小)相反,RSS 是进程当前使用的内存。这是当前进程正在使用多少 RAM 的实际数字(以千字节为单位)。
关于 RSS 与 VSZ,我想已经说了很多。从管理员/程序员/用户的角度来看,当我设计/编写应用程序时,我更关心 RSZ(常驻内存),当您不断拉动越来越多的变量(堆积)时,您会看到这个值猛增。尝试一个简单的程序在循环中构建基于 malloc 的空间分配,并确保在该 malloc 空间中填充数据。 RSS 不断上升。就 VSZ 而言,它更像是 linux 所做的虚拟内存映射,其核心特性之一源自传统的操作系统概念。 VSZ 管理由内核的虚拟内存管理完成,有关 VSZ 的更多信息,请参阅 Robert Love 对 mm_struct 和 vm_struct 的描述,它们是内核中基本 task_struct 数据结构的一部分。
总结@jmh excellent answer:
在#linux 中,进程的内存包括:
它自己的二进制文件
它的共享库
它的栈和堆
由于分页,并非所有这些都始终完全在内存中,只有有用的、最近使用的部分(页面)才是。其他部分被调出(或换出)以为其他进程腾出空间。
下表取自@jmh 的回答,显示了特定进程的常驻内存和虚拟内存的示例。
+-------------+-------------------------+------------------------+
| portion | actually in memory | total (allocated) size |
|-------------+-------------------------+------------------------|
| binary | 400K | 500K |
| shared libs | 1000K | 2500K |
| stack+heap | 100K | 200K |
|-------------+-------------------------+------------------------|
| | RSS (Resident Set Size) | VSZ (Virtual Set Size) |
|-------------+-------------------------+------------------------|
| | 1500K | 3200K |
+-------------+-------------------------+------------------------+
总结一下:驻留内存是现在物理内存中的实际内存,而虚拟大小是加载所有组件所需的总物理内存。
当然,数字不会相加,因为库在多个进程之间共享,并且它们的内存为每个进程单独计算,即使它们的单个实例存在于内存中。
它们不是托管的,而是可测量的,并且可能是有限的(参见 getrlimit
系统调用,也在 getrlimit(2) 上)。
RSS 表示 resident set size(位于 RAM 中的虚拟地址空间的一部分)。
您可以使用 proc(5) 和 cat /proc/1234/maps
查询进程 1234 的 virtual address space 及其状态(包括内存消耗)到 cat /proc/1234/status
libxml2.so
,则共享库将计入它们的每个 RSS,因此它们的 RSS 总和将超过实际使用的内存。top
命令显示 1.2G RES 和 4.5G VIRT。该系统没有任何交换,swapon --show
不返回任何内容。你怎么解释这个?如果vsz是swap+共享库,这种情况下共享库都超过3.3G?可能吗?只是真的很迷茫...