把稳所有测试是 5.13.0-52内核条件下测试的,不同的内核测试环境,内存分类会有很大的不同。
二 程序内存构造在linux 32位系统中默认虚拟的内存布局如下:
解释:
(图片来自网络侵删)在linux中每个进程都有各自的虚拟内存空间,空间的大小和cpu的位数决定了虚拟空间的上限,比如在32位系统下,硬件可以访问的内存空间上限是4GB,这4GB的空间也不是完备可以给运用程序利用。
全体内存空间分为两个部分,操作系统占用一部分,从地址0xC0000000到0xFFFFFFFF这1GB的空间。剩下的从0x00000000到0xBFFFFFFF共3GB空间共3GB空间;
ELF可实行文件将全体虚拟内存空间分为多个segment;
操作系统是通过VMA 对进程的虚拟内存空间进行管理的。
VMA 是virtual Memory Area的简称,一个大略的程序VMA展示如下:
root@ubuntu-lab:/sys/kernel/debug/tracing# cat /proc/9776/maps5655a000-5655b000 r--p 00000000 fd:00 1193979 /home/miao/c-test/mm-test/a.out5655b000-5655c000 r-xp 00001000 fd:00 1193979 /home/miao/c-test/mm-test/a.out5655c000-5655d000 r--p 00002000 fd:00 1193979 /home/miao/c-test/mm-test/a.out5655d000-5655e000 r--p 00002000 fd:00 1193979 /home/miao/c-test/mm-test/a.out5655e000-5655f000 rw-p 00003000 fd:00 1193979 /home/miao/c-test/mm-test/a.out5746c000-5748e000 rw-p 00000000 00:00 0 [heap]f7d83000-f7da3000 r--p 00000000 fd:00 546008 /usr/lib32/libc.so.6f7da3000-f7f1f000 r-xp 00020000 fd:00 546008 /usr/lib32/libc.so.6f7f1f000-f7fa4000 r--p 0019c000 fd:00 546008 /usr/lib32/libc.so.6f7fa4000-f7fa5000 ---p 00221000 fd:00 546008 /usr/lib32/libc.so.6f7fa5000-f7fa7000 r--p 00221000 fd:00 546008 /usr/lib32/libc.so.6f7fa7000-f7fa8000 rw-p 00223000 fd:00 546008 /usr/lib32/libc.so.6f7fa8000-f7fb2000 rw-p 00000000 00:00 0 f7fbc000-f7fbe000 rw-p 00000000 00:00 0 f7fbe000-f7fc2000 r--p 00000000 00:00 0 [vvar]f7fc2000-f7fc4000 r-xp 00000000 00:00 0 [vdso]f7fc4000-f7fc5000 r--p 00000000 fd:00 546004 /usr/lib32/ld-linux.so.2f7fc5000-f7fe8000 r-xp 00001000 fd:00 546004 /usr/lib32/ld-linux.so.2f7fe8000-f7ff5000 r--p 00024000 fd:00 546004 /usr/lib32/ld-linux.so.2f7ff6000-f7ff8000 r--p 00031000 fd:00 546004 /usr/lib32/ld-linux.so.2f7ff8000-f7ff9000 rw-p 00033000 fd:00 546004 /usr/lib32/ld-linux.so.2ffe18000-ffe39000 rw-p 00000000 00:00 0 [stack]
解释:
第一列VMA的地址范围;(虚拟地址)第二列VMA的权限,r标识可读,w标识可写,x标识可实行,p 标识私有,s标识共享;第三列偏移,表示VMA对应的Segment在映像文件中的便宜;第四列一样平常表示映像文件所在设备的主设备号:次设备号,这里面主设备号大多显示为fd,难道是一个缘故原由?非文件映射的内存,比如堆和栈,则这两位显示为00:00第五列 标识映射文件的节点号;第六列标识映射的详细文件,可以看到除了程序文件外,还有利用的库的文件信息。 vdso 为分外的VMA,用于和内核进行交互。采取的代码如下:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>int g_int = 123;static g_static_int2 = 456;static g_static_int_not_init;int main(void){ int l_int = 3; int l_int2 = 4; static l_static_int =6; static l_static_int2; int pint = (int)malloc(sizeof(int)); pint = 12; printf("g_int:%d,\tg_static_int2:%d \tg_static_int_not_init:%d \n",g_int,g_static_int2,g_static_int_not_init); printf("g_int:%p,\tg_static_int2:%p \tg_static_int_not_init:%p \n",&g_int,&g_static_int2,&g_static_int_not_init); printf("l_int:%d \tl_int2:%d \tl_static_int:%d,\tl_static_int2:%d,\tpint:%d\n",l_int,l_int2,l_static_int,l_static_int2,pint); printf("l_int:%p \tl_int2:%p \tl_static_int:%p,\tl_static_int2:%p,\tpint:%p\n",&l_int,&l_int2,&l_static_int,&l_static_int2,pint); while(1) { sleep(3); printf("PID:%d\n",getpid()); } free(pint); return 0;}
多次运行可以创造我们变量的地址在其该当对应的空间内,同时创造每次堆和stack的地址是每次不同的,这也是为了安全期间,设置的随机偏移。
顺便说下stack为主线程的栈,最大的大小默认是ulimit -s ,一样平常为8MB,pthread_create 创建的栈大小一样平常为2MB,不同架构不同而且和ulimit 设置的大小有关,也可以自行变动。
三 打算程序内存大小至于程序内存利用大小,比较大略的方法是top -p pid 直接看到如下:
PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND 12375 miao 20 0 2764 852 788 S 0.0 0.0 0:00.00 a.out
VIRT即虚拟内存大小,RES即实际内存大小,这两个一样平常最主要,就够了。 如果按照每个VMA打算,求和,可以通过/proc/pid/smaps文件去打算,这个文件比maps的文件更详细,值得仔细剖析,打算验证下:
cat /proc/12375/smaps|grep Size|grep -v Page|awk -F: '{print $2}'|awk '{sum += $1}; END {print sum}'2764
虚拟内存对的上,实际内存打算:
# cat /proc/12375/smaps|grep Rss|grep -v Page|awk -F: '{print $2}'|awk '{sum += $1}; END {print sum}'1388
这个和实际内存对不上,按照Pss(即共享内存做了平分处理后,仍旧有差距),差距缘故原由是运用申请内存通过c的库申请的,库申请的时候也会多申请一些,还有一些对齐之类的,有些差异可能也正常。
还有个比较大略的打算程序内存的方法:
oot@ubuntu-lab:/home/miao/c-test/mm-test# cat /proc/24546/statusName: a.outUmask: 0002State: S (sleeping)Tgid: 24546Ngid: 0Pid: 24546PPid: 5359TracerPid: 0Uid: 1000 1000 1000 1000Gid: 1000 1000 1000 1000FDSize: 256Groups: 4 24 27 30 46 110 1000 NStgid: 24546NSpid: 24546NSpgid: 24546NSsid: 5359VmPeak: 1051332 kBVmSize: 1051332 kBVmLck: 0 kBVmPin: 0 kBVmHWM: 1049776 kBVmRSS: 1049776 kBRssAnon: 1048672 kBRssFile: 1104 kBRssShmem: 0 kB....
VmRSS 这个即是程序占用的内存,一样平常情形下VmRSS = RssAnon+RssFile+RssShmem
四 系统内存剖析实在我们碰着系统的性能问题,如果疑惑是内请安题,那么很有可能用free命令看下,然后top命令看看,top下对程序的占用内存情形进行排序,找到可疑进程,然后再做上面的进程占用内存情形的剖析。(全体系统所占内存包括内核占的内存和运用程序所占内存两个部分)。
实在最该看的两个内核导出文件:/proc/meminfo和 /proc/vmstat 前者是内存的占用分类情形,而后者是内存分配,规整、脏页回写等更细节的内存数据的动态变革,通过这些变革创造问题,由于后者不是重点,重点来看下前者,在我机器上统计如下:
miao@ubuntu-lab:~$ cat /proc/meminfo MemTotal: 4926744 kB //所有可用的内存大小,物理内存减去预留位和内核利用。系统从加电开始到勾引完成,firmware/BIOS要预留一些内存,内核本身要占用一些内存,末了剩下可供内核支配的内存便是MemTotal。这个值在系统运行期间一样平常是固定不变的,重启会改变。MemFree: 3663620 kB //表示系统尚未利用的内存。MemAvailable: 4209668 kB //真正的系统可用内存,系统中有些内存虽然已被利用但是可以回收的,比如cache/buffer、slab都有一部分可以回收,以是这部分可回收的内存加上MemFree才是系统可用的内存Buffers: 78416 kB //用来给块设备做缓存的内存,(文件系统的 metadata、pages)Cached: 661976 kB //分配给文件缓冲区的内存,例如vi一个文件,就会将未保存的内容写到该缓冲区SwapCached: 0 kB //被swap到磁盘上的匿名内存,又一次被拉入内存统计Active: 325864 kB //常常利用的高速缓冲存储器页面文件大小Inactive: 618264 kB //不常常利用的高速缓冲存储器文件大小Active(anon): 4564 kB //生动的匿名内存Inactive(anon): 215464 kB //不生动的匿名内存Active(file): 321300 kB //生动的文件利用内存Inactive(file): 402800 kB //不生动的文件利用内存Unevictable: 19372 kB //不能被开释的内存页Mlocked: 19372 kB //系统调用 mlock 家族许可程序在物理内存上锁住它的部分或全部地址空间。这将阻挡Linux 将这个内存页调度到交流空间(swap space),纵然该程序已有一段韶光没有访问这段空间SwapTotal: 4194300 kB //交流空间总内存SwapFree: 4194300 kB //交流空间空闲内存Dirty: 148 kB //等待被写回到磁盘的脏内存Writeback: 0 kB //正在被写回的脏内存AnonPages: 223144 kB //未映射页的内存/映射到用户空间的非文件页表大小Mapped: 210380 kB //映射文件内存Shmem: 13168 kB //已经被分配的共享内存,所有tmpfs类型的文件系统占用的空间都计入共享内存KReclaimable: 60332 kB Slab: 137076 kB //内核数据构造缓存SReclaimable: 60332 kB //可收回slab内存SUnreclaim: 76744 kB //不可收回slab内存KernelStack: 7568 kB // 每一个用户线程都会分配一个kernel stack(内核栈),内核栈虽然属于线程,但用户态的代码不能访问,只有通过系统调用(syscall)、自陷(trap)或非常(exception)进入内核态的时候才会用到,也便是说内核栈是给kernel code利用的。在x86系统上Linux的内核栈大小是固定的8K或16KPageTables: 5876 kB //管理内存分页的索引表(物理内存和虚拟内存映射表)的大小 NFS_Unstable: 0 kB // The amount, in kibibytes, of NFS pages sent to the server but not yet committed to the stable storage.Bounce: 0 kB // 有些老设备只能访问低端内存,比如16M以下的内存,当运用程序发出一个I/O 要求,DMA的目的地址却是高端内存时(比如在16M以上),内核将在低端内存等分配一个临时buffer作为跳转,把位于高端内存的缓存数据复制到此处。这种额外的数据拷贝被称为“bounce buffering”,会降落I/O 性能。大量分配的bounce buffers 也会占用额外的内存。WritebackTmp: 0 kB // USE用于临时写回缓冲区的内存CommitLimit: 6657672 kB // 系统实际可分配内存总量Committed_AS: 1742228 kB // 当前已分配的内存总量VmallocTotal: 34359738367 kB // 虚拟内存空间能分配的总内存大小VmallocUsed: 57524 kB // 虚拟内存空间利用的内存大小VmallocChunk: 0 kB // 虚拟内存空间可分配的最大的逻辑连续的内存大小Percpu: 89600 kBHardwareCorrupted: 0 kBAnonHugePages: 0 kB //AnonHugePages统计的是Transparent HugePages (THP),THP与Hugepages不是一回事,与进程的RSS/PSS是有重叠的,如果用户进程用到了THP,进程的RSS/PSS也会相应增加ShmemHugePages: 0 kBShmemPmdMapped: 0 kBFileHugePages: 0 kBFilePmdMapped: 0 kBHugePages_Total: 0 //超级大页总大小如果进程利用了Hugepages,它的RSS/PSS不会增加。HugePages_Free: 0 //超级大页空闲大小HugePages_Rsvd: 0 // 超级大页剩余内存HugePages_Surp: 0 // 剩余超级大页数量Hugepagesize: 2048 kB //超级大页 尺寸为2MBHugetlb: 0 kBDirectMap4k: 198464 kB //DirectMap所统计的不是关于内存的利用,而是一个反响TLB效率的指标 表示映射为4kB的内存数量 TLB(Translation Lookaside Buffer)是位于CPU上的缓存,用于将内存的虚拟地址翻译成物理地址,由于TLB的大小有限,不能缓存的地址就须要访问内存里的page table来进行翻译,速率慢很多。DirectMap2M: 3913728 kB // 表示映射为2MB的内存数量DirectMap1G: 1048576 kB // 表示映射为1GB的内存数量
五 申请内存剖析刚才的系统内存剖析,可以剖析各种内存的大小,还须要根据不同种类的内存大小,对应到运用程序里面,是运用程序中申请哪类内存引起的那,以是这个章节是通过测试程序申请内存看meminfo中各项内存分类的大小变革,从而等到碰着问题的时候就可以通过meminfo中的数据预测到底是哪里数据影响的。
下图左边是程序通过glibc的库申请内存,把稳这里面的程序也可能是c程序,也可能是java程序,很多措辞终极底层申请内存还是通过c的库来申请,c的库申请内存的内存紧张就有两种形式,一种是mmap映射的办法,对应虚拟内存的映射内存区,另一种办法是通过brk或sbrk来申请小内存(一样平常是小于128kb的内存),这些虚拟内存并没有真实分配,只有真正利用的时候通过缺页中断,分配真实的物理内存。
图来自极客韶光
按照内存类型对程序运行所需的内存进行分类,构成如下的思维导图:
重点须要关注的:
私有匿名内存,比如我们通过malloc或calloc、或new申请的内存。共享匿名内存,tmpfs这里面如果你程序写临时文件写这里面,须要自己卖力删除。私有文件映射,比如通过mmap映射读文件。共享文件映射,如果自己申请须要自己开释。5.1 malloc申请内存-匿名内存测试
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#define SIZE 102410241024int main (void){ char p = (char ) malloc(SIZE); memset(p,0x0,SIZE); while(1) { printf("PID:%d\n",getpid()); sleep(50); }free(p);return 0;}
先清理下内存,然后运行此程序,查看/proc/meminfo的变革。
root@ubuntu-lab:/home/miao/c-test/mm-test# diff meminfo.old meminfo.new2,5c2,5< MemFree: 4217504 kB< MemAvailable: 4230356 kB< Buffers: 2040 kB< Cached: 218572 kB---> MemFree: 3165428 kB> MemAvailable: 3180980 kB> Buffers: 4396 kB> Cached: 218776 kB7,8c7,8< Active: 37908 kB< Inactive: 380112 kB---> Active: 40424 kB> Inactive: 1428872 kB10,12c10,12< Inactive(anon): 211228 kB< Active(file): 35272 kB< Inactive(file): 168884 kB---> Inactive(anon): 1259804 kB> Active(file): 37788 kB> Inactive(file): 169068 kB17c17< Dirty: 204 kB---> Dirty: 12 kB19,20c19,20< AnonPages: 217032 kB< Mapped: 213968 kB---> AnonPages: 1265628 kB> Mapped: 213988 kB22,27c22,27< KReclaimable: 33828 kB< Slab: 109880 kB< SReclaimable: 33828 kB< SUnreclaim: 76052 kB< KernelStack: 7472 kB< PageTables: 5576 kB---> KReclaimable: 33832 kB> Slab: 109808 kB> SReclaimable: 33832 kB> SUnreclaim: 75976 kB> KernelStack: 7456 kB> PageTables: 7628 kB32c32< Committed_AS: 1732340 kB---> Committed_AS: 2781300 kB
重点几个:
1. Inactive(anon) 增加1GB的非生动匿名内存;2. Committed_AS 分配的内存增加了1GB;3. Inactive 非生动匿名内存增加1GB;4. AnonPages 匿名内存页增加了1GB;5. MemAvailable 和MemFree 减少了1GB。
5.2 mmap申请私有匿名内存
#include <stdlib.h>#include <stdio.h>#include <strings.h>#include <sys/mman.h>#include <sys/stat.h>#include <sys/types.h>#include <fcntl.h>#include <unistd.h>#define MEMSIZE 102410241024#define MPFILE "./mmapfile"int main(){ void ptr; int fd; fd = open(MPFILE, O_RDWR); if (fd < 0) { perror("open()"); exit(1); }// 匿名办法申请的时候会忽略末了两个参数的 ptr = mmap(NULL, MEMSIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, fd, 0); if (ptr == NULL) { perror("mmap()"); exit(1); } printf("%p\n", ptr); bzero(ptr, MEMSIZE); printf("pid=%d\n", getpid()); sleep(50); munmap(ptr, MEMSIZE); close(fd); exit(1);}
结果同上。
5.3 mmap申请匿名共有映射和上面代码类似,只是一句代码不同:
ptr = mmap(NULL, MEMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_ANONYMOUS, fd, 0);
紧张变革内存:
MemFree: 空闲内存减少1GB。MemAvailable: 可用内存减少1GB。Cached: 缓存增加1GB。Inactive: 增加了1GB。Inactive(anon): 增加1GB。Mapped: 增加1GB。Shmem: 共享内存增加了1GB。Committed_AS: 申请内存增加了1GB。
5.4 mmap申请私有文件映射内存和上面的代码类似,只是:
ptr = mmap(NULL, MEMSIZE, PROT_READ|PROT_WRITE, MAP_PRIVATE, fd, 0);
紧张变革内存:
1. MemFree 空闲内存少了2GB。2. MemAvailable 内存少了1GB,由于缓存是可以开释的。3. Cached 增加了1GB。4. Inactive 增加了2GB。5. Inactive(anon) 增加了1GB。6. Inactive(file) 增加了1GB。7. AnonPages 增加了1GB8. Committed_AS 增加了1GB。
私有文件映射,在进程内存种看到的是占用Inactive(file)内存,只以是也会占用Inactive(anon) ,是在于私有文件映射很分外,它在写的时候,不会同步到后台的文件上去,采取写时复制,写时候会拷贝一份到物理内存中(匿名内存)。
5.5 mmap申请共享文件映射代码和上面类似不同点:
ptr = mmap(NULL, MEMSIZE, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
结果如下:
1. MemFree 少1GB。2. Cached 增加了1GB。3. Inactive 增加了1GB。4. Inactive(file) 增加了1GB。5. Mapped 增加了1GB。
把稳只有共享内存的mmap才会在Mapped内存,共享内存映射算Cache以是这里面增加了,由于第一次读文件,所以是Inactive(file),以是增加了1GB。
把稳这种办法有两个有用的点:
映射的内存是共享的,以是可以多个在多个进程间共享。对映射的内存写入或修正后,系统会自动同步到对应的文件中,这个很好用。总结
只假如私有的mmap映射,对付系统看来都是匿名页面。只假如共享的mmap映射,对系统占用的内存都是MMaped内存。5.6 shm共享内存代码如下:
#include <stdio.h>#include <stdlib.h>#include <unistd.h>#include <sys/ipc.h>#include <sys/shm.h>#include <string.h>#define MEMSIZE 102410241024intmain(){ int shmid; char ptr; pid_t pid; struct shmid_ds buf; int ret; // 创建1GB大小权限为0600的共享内存 shmid = shmget(IPC_PRIVATE, MEMSIZE, 0600); if (shmid<0) { perror("shmget()"); exit(1); } // 获取共享内存信息复制到buf中,包括权限大小等 ret = shmctl(shmid, IPC_STAT, &buf); if (ret < 0) { perror("shmctl()"); exit(1); } printf("shmid: %d\n", shmid); printf("shmsize: %d\n", buf.shm_segsz); pid = fork(); if (pid<0) { perror("fork()"); exit(1); } // 子进程 if (pid==0) { // 将共享内存映射到本进程的内存空间 ptr = shmat(shmid, NULL, 0); if (ptr==(void)-1) { perror("shmat()"); exit(1); } bzero(ptr, MEMSIZE); // 拷贝hello到里面去 strcpy(ptr, "Hello!"); exit(0); } else { // 等子进程写入结束 wait(NULL); // 将共享内存映射到本进程的内存空间 ptr = shmat(shmid, NULL, 0); if (ptr==(void)-1) { perror("shmat()"); exit(1); } // 输出退出 puts(ptr); exit(0); }}
把稳:
代码没有调用int shmdt(const void shmadr); 来清理共享内存和进程的关联;代码也未调用 shmctl的IPC_RMID删除共享内存 来删除内存,以是程序运行结束,还是会占用共享内存的,如下查看:
root@ubuntu-lab:/home/miao/c-test/mm-test# ipcs -m------ Shared Memory Segments --------key shmid owner perms bytes nattch status 0x000631ba 0 postgres 600 56 6 0x00000000 3 root 600 1073741824 0
连续看下内存的变革:
1. MemFree 和MemAvailable 减少了1GB。2. Cached 占用增加了1GB,可见shm是属于cache的。3. Inactive 增加了1GB。4. Inactive(anon) 增加了1GB。5. Shmem 增加了1GB。6. Committed_AS增加了1GB。
shm被视为基于tmpfs文件系统的内存页,既然基于文件系统,就不算匿名页,以是不被计入/proc/meminfo中的AnonPages。
清理共享内存:
root@ubuntu-lab:/home/miao/c-test/mm-test# ipcrm -m 3root@ubuntu-lab:/home/miao/c-test/mm-test# ipcs -m------ Shared Memory Segments --------key shmid owner perms bytes nattch status 0x000631ba 0 postgres 600 56 6
5.7 tmpfs测试:
mkdir /tmp/tmpfsmount -t tmpfs -o size=2G none /tmp/tmpfs/#占用空间root@ubuntu-lab:/home/miao/c-test/mm-test# dd if=/dev/zero of=/tmp/tmpfs/testfile bs=1G count=11+0 records in1+0 records out1073741824 bytes (1.1 GB, 1.0 GiB) copied, 3.15495 s, 340 MB/sroot@ubuntu-lab:/home/miao/c-test/mm-test# root@ubuntu-lab:/home/miao/c-test/mm-test# df -hnone 2.0G 1.0G 1.0G 50% /tmp/tmpfs
内存变革:
1. MemFree 和MemAvailable 减少了1GB。2. Cached 占用增加了1GB,可见shm是属于cache的。3. Inactive 增加了1GB。4. Inactive(anon) 增加了1GB。5. Shmem 增加了1GB。6. Committed_AS增加了1GB。
和shm一样的内存变革,把稳用:echo 3 > /proc/sys/vm/drop_caches 并不会开释内存,而且通过free -h 可以看到有1GB的空间。
root@ubuntu-lab:/home/miao/c-test/mm-test# free -h total used free shared buff/cache availableMem: 4.7Gi 474Mi 3.0Gi 1.0Gi 1.2Gi 3.0Gi
清理:
rm /tmp/tmpfs/testfile umount /tmp/tmpfs/
参考:
[linux内存占用剖析之meminfo - SegmentFault 思否](https://segmentfault.com/a/1190000022518282)[/proc/meminfo之谜 | Linux Performance](http://linuxperf.com/?p=142)代码来自:深入浅出Linux 内核管理和调试[https://www.jianshu.com/p/eece39beee20](https://www.jianshu.com/p/eece39beee20)