VIRT 与 RSE 占用分析

1、概述

在Linux命令行中执行top命令,可以查询到所有进程使用的VIRT虚拟内存、RES常驻内存和共享内存SHR。那么,什么是VIRT虚拟内存、RES常驻内存和共享内存SHR?我们编写的Linux C++程序如何影响它们呢?

VIRT:

- 进程“需要的”虚拟内存大小,包括进程使用的库、代码、数据,以及malloc、new分配的堆空间和分配的栈空间等;
- 假如进程新申请10MB的内存,但实际只使用了1MB,那么它会增长10MB,而不是实际的1MB使用量。
- VIRT = SWAP + RES

RES:

- 进程当前使用的内存大小,包括使用中的malloc、new分配的堆空间和分配的栈空间,但不包括swap out量;
- 包含其他进程的共享;
- 如果申请10MB的内存,实际使用1MB,它只增长1MB,与VIRT相反;
- 关于库占用内存的情况,它只统计加载的库文件所占内存大小。
- RES = CODE + DATA

SHR:

- 除了自身进程的共享内存,也包括其他进程的共享内存;
- 虽然进程只使用了几个共享库的函数,但它包含了整个共享库的大小;
- 计算某个进程所占的物理内存大小公式:RES – SHR;
- swap out后,它将会降下来。

2、程序验证

2.1 申请但未使用的堆空间不占用RES,但占用VIRT

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <string.h>
#include <stdio.h>

int main() {
char * p = new char [1024*1024*512];
getchar();
return 0;
}

top显示如下:

VIRT包含了new出来的512MB空间,但是RES不包含该空间。即malloc或new出来的空间,如果没有使用,会放入SWAP中,并不在内容中真实的分配物理内存。

2.2 申请且使用了的堆空间占用RES

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <string.h>
#include <stdio.h>

int main() {
char * p = new char [1024*1024*512];
memset(p, 0, 1024*1024*512); //使用申请的空间
getchar();
return 0;
}

top显示如下:

VIRT包含new出来的512MB空间,RES包含目前使用的memset的512M空间。即new出来的空间被使用后,会真实分配物理内存。

使用部分申请了的堆空间则RES显示使用的部分

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <string.h>
#include <stdio.h>

int main() {
char * p = new char [1024*1024*512];
memset(p + 1024*1024*128, 0, 1024*1024*128);
getchar();
return 0;
}

top显示如下:

VIRT包含new出来的512MB空间,RES包含目前使用的memset的128M空间。即new出来的空间,如果只使用部分,则只分配部分物理内存。

2.3 申请但未使用的栈空间不占用RES,但占用VIRT

1
2
3
4
5
6
7
8
9
#include <iostream>
#include <string.h>
#include <stdio.h>

int main() {
char p[1024*1024*20];
getchar();
return 0;
}

top显示如下:

没有使用的栈空间,VIRT会包含(没有使用的栈空间会在SWAP中)。

2.4 已经使用的栈空间,VIRT和RES都会包含

1
2
3
4
5
6
7
8
9
10
#include <iostream>
#include <string.h>
#include <stdio.h>

int main() {
char p[1024*1024*20];
memset(p, 0, 1024*1024*20);
getchar();
return 0;
}

top显示如下:

2.5 局部申请的内存释放后RES并未减少

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <unistd.h>

void test_part_virt(int len) {
int i = len+1;
char *p = new char[1024*1024*i*10];
printf("i=%d, p=%p\n", i, p);
memset(p, 0, 1024*1024*i*10);
delete [] p;
}

int main() {
//char * p = new char [1024*1024*512];
char p[1024*1024*20];
memset(p, 0, 1024*1024*20);
for(int i=0; i<20; ++i) {
sleep(10);
test_part_virt(i%4);
}
getchar();
return 0;
}

top显示如下:

currlwent i=1, p=0xfd0010 时RES开始增加,每次增加10M, 且一直占用,此问题主要由于Glibc的内存分配由brk和mmap两种内存内存分配策略导致。
从操作系统的角度看,进程的内存分配由两个系统调用完成:brk和mmap。brk是将数据段(.data)的最高地址指针_edata往高地址推,mmap是在进程的虚拟地址空间中找一块空闲的。其中,mmap分配的内存由munmap释放,内存释放时将立即归还操作系统;而brk分配的内存需要等到高地址内存释放以后才能释放,可通过在程序启动时增加以下代码,修改内存分配方式来解决问题

mallopt(M_MMAP_MAX, 0); // 禁止malloc调用mmap分配内存
mallopt(M_TRIM_THRESHOLD, 0); // 禁止内存缩进,sbrk申请的内存释放后不会归还给操作系统

修改后的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#include <iostream>
#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <malloc.h>

void test_part_virt(int len) {
int i = len+1;
char p[1024*1024*i*10];
memset(p, 0, 1024*1024*i*10);
printf("currlwent i=%d, p=%p\n", i, p);
//char *p = new char[1024*1024*i*10];
//printf("currlwent i=%d, p=%p\n", i, p);
//memset(p, 0, 1024*1024*i*10);
//delete [] p;
}

int main() {
mallopt(M_MMAP_MAX, 0); // 禁止malloc调用mmap分配内存
//char * p = new char [1024*1024*512];
char p[1024*1024*20];
memset(p, 0, 1024*1024*20);
for(int i=0; i<20; ++i) {
sleep(10);
test_part_virt(i%4);
}
getchar();
return 0;
}

经过以上的代码修改程序RES内存稳定再20MB

参考:

https://blog.csdn.net/huyiyang2010/article/details/7815491#
https://blog.csdn.net/hmylk/article/details/37761247
https://bbs.csdn.net/topics/330179712