前几天同事遇到一个问题,程序启动加载所有的功能模块,后续通过接口调用卸载了一些功能模块,但是在资源占用测试时,发现卸载前后比较,只有cpu使用率下降了,但是内存占用没有下降,这比较奇怪,卸载模块时明明已经free掉了申请的内存的啊,可是内存占用还是没有下降喃?于是我开始去搜索了相关的问题,发现果然大有门道啊。
虚拟内存空间
先说说虚拟内存,虚拟地址空间的内部又被分为内核空间和用户空间两部分,不同位数的处理器,地址空间的范围也不同。以32位处理器为例,如下如所示:
在用户空间,从高到低分别为
- 栈,主要为局部变量,大小一般固定为8MB
- 文件映射段,主要为动态库、共享内存等
- 堆,主要为动态分配的内存,从低地址往高地址增长
- 数据段,主要为全局变量
- 只读段,主要为常量和代码
malloc对应的系统调用
在我们使用malloc在堆中动态分配内存时,实际上会设计到两种系统调用,分别为brk和mmap。
这两种系统调用是有区别的:
brk
当我们申请的内存大小小于128k时,标准库会调用brk来分配,它是将堆顶移动的方式进行的。如下如所示:
mmap
而申请的内存大小大于128k时,会调用mmap内存映射来分配,即在文件映射段找一块空闲的内存进行分配。如下如所示:
从两种分配的方式可以看到,brk将不用的内存缓存起来,可以减小缺页异常的发生。正由于如此,这些内存没有立刻释放,就导致了进程内存占用不会下降。而mmap的方式只适合大内存的使用,因为频繁的缺页异常会增加内核管理的负担。
例外需要特别注意的是,调用brk或mmap仅仅分配了虚拟内存,没有真正的分配内存,只有在首次访问时才会分配,即当进程访问的虚拟地址在页表中查不到时,系统会产生缺页异常,进入内核空间分配物理内存、更新进程页表,再返回用户空间继续进程的运行。
在内存泄露时,进程占用的内存会不断增大,此时系统也进行一些相应的处理,例如通过LRU算法回收最近最少使用的内存页面,或者通过交换分区,把不常用的内存直接写到磁盘。甚至通过OOM直接杀死内存占用量大的进程。
如何查看内存使用情况
linux下有好几个工具可以用于查看内存的使用情况,例如
free
$ free
total used free shared buff/cache available
Mem: 7868592 3065668 1583040 613104 3219884 3952152
Swap: 0 0 0
从结果可以看到,第一行Mem为物理内存使用情况,第二行Swap为交换分区的使用情况。total表示内存总的大小,used表示已经使用的内存大小,free表示未使用的内存大小,shared表示共享内存的大小,buff/cache表示缓存和缓冲区的大小,avaliable表示新进程可用的内存大小。
top
Tasks: 244 total, 2 running, 192 sleeping, 0 stopped, 1 zombie
%Cpu(s): 24.1 us, 7.0 sy, 0.2 ni, 68.0 id, 0.7 wa, 0.0 hi, 0.1 si, 0.0 st
KiB Mem : 7868592 total, 1473960 free, 3132276 used, 3262356 buff/cache
KiB Swap: 0 total, 0 free, 0 used. 3846844 avail Mem
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
29717 root 20 0 3124696 380616 206656 S 12.5 4.8 16:32.65 Web Content
32060 root 20 0 43712 3996 3304 R 12.5 0.1 0:00.02 top
13849 root 20 0 687096 38360 26448 S 6.2 0.5 2:15.43 gnome-terminal-
从结果可以看到,KiB Mem和KiB Swap这两行的内容和free是一样的,主要看下面和具体进程相关的字段。VIRT 是进程虚拟内存的大小,包含进程申请过的内存。RES 进程实际使用的物理内存大小,但不包括 Swap 和共享内存。SHR 是共享内存的大小,比如与其他进程共同使用的共享内存、加载的动态链接库以及程序的代码段等。%MEM 是进程使用物理内存占系统总内存的百分比。
vmstat
$ vmstat 1 2
procs -----------memory---------- ---swap-- -----io---- -system-- ------cpu-----
r b swpd free buff cache si so bi bo in cs us sy id wa st
1 0 0 1025268 548532 2743244 0 0 46 210 136 4 24 7 68 1 0
1 0 0 1021028 548532 2747396 0 0 0 172 1124 5826 5 3 89 3 0
vmstat就不再过多介绍了,以上命令都可以通过man command的方式获取各个字段的含义。当然还有ps可以查询到内存的使用情况。