内存测量
系统占用内存
free
旧版本free
$ free
             total       used       free     shared    buffers     cached
Mem:      65960636   63933576   2027060   73392    1602076   32628548
-/+ buffers/cache:       29702952   36257684
Swap:            0          0          0
(1)第一行Mem:内存的使用情况,默认单位是Kb。
– total:系统总共的内存。total=used+free
– used:已经被使用的内存。
– free:剩余还没有被使用的物理内存。
– shared: 多个进程共享的内存。
– buffers:块设备所占的缓存页,包括直接读写块设备、文件系统元数据(metadata)、SupperBlock所使用的缓存页等。
– cached:普通文件数据所占用的缓存页。
(2)第二行-/+ buffers/cache:物理内存缓存统计。
-buffers/cache(used列):29702952,被程序正在使用的缓存内存,等于used-buffers-cached
+buffers/cache(used列):36257684,还可以挪用使用的缓存内存,等于free+buffers+cached
(3)第三行:交换区空间的统计。
新版本free
root@TinaLinux:/# free
              total        used        free      shared  buff/cache   available
Mem:        2022788      98876     1818264    84      105648     1898696
Swap:           0           0           0
第一行Mem:内存的使用情况,默认单位是Kb。
第二行Swap:交换空间的使用情况。
– total:系统总共的内存。total = used+free+buff/cache
– used:已经被使用的内存。
– free:剩余还没有被使用的物理内存。
– shared: 多个进程共享的内存。
– buff:块设备所占的缓存页。
– cache:普通文件数据所占用的缓存页。
– available:还可以被应用程序使用的物理内存大小。available=free+可回收利用的buff/cache
在系统中available才是系统真正可还能申请到的内存。
/proc/meminfo
# cat /proc/meminfo 
MemTotal:        2022788 kB
MemFree:         1818376 kB
MemAvailable:    1898700 kB
Buffers:             436 kB
Cached:            20352 kB
SwapCached:            0 kB
Active:             5104 kB
Inactive:          31432 kB
Active(anon):        116 kB
Inactive(anon):    15716 kB
Active(file):       4988 kB
Inactive(file):    15716 kB
Unevictable:           0 kB
Mlocked:               0 kB
SwapTotal:             0 kB
SwapFree:              0 kB
Dirty:                 0 kB
Writeback:             0 kB
AnonPages:         15792 kB
Mapped:             5156 kB
Shmem:                84 kB
KReclaimable:      84752 kB
Slab:             148640 kB
SReclaimable:      84752 kB
SUnreclaim:        63888 kB
KernelStack:        2144 kB
PageTables:          620 kB
NFS_Unstable:          0 kB
Bounce:                0 kB
WritebackTmp:          0 kB
CommitLimit:     1011392 kB
Committed_AS:      93972 kB
VmallocTotal:   259653632 kB
VmallocUsed:        4820 kB
VmallocChunk:          0 kB
Percpu:              768 kB
CmaTotal:          65536 kB
CmaFree:           48764 kB
- MemTotal: 系统当前可用物理内存总量,除去了reserved的内存。
- MemFree:系统当前剩余空闲物理内存。
- MemAvailable:系统中可使用的物理内存,包含了可以回收的内存。
- Buffers:块设备缓存。
- Cached:普通文件页的缓存。
- SwapCached:系统有多少匿名页面曾经被交换到交换区过。
- Active:活动的匿名页面和活动的文件映射页面内存,=Active(anon)+Active(file)。
- Inactive:不活跃的匿名页面和文件页面。=Inactive(anon)+Inactive(file)。
- Active(anon):活跃的匿名页面。
- Inactive(anon): 不活跃的匿名页面。
- Active(file):活跃的文件页面。
- Inactive(file): 不活跃的文件页面。
- Unevictable: 不能回收的页面(LRU_UNEVICTABLE)。
- Mlocked: 不能被交换(swap)出去的页面。
- SwapTotal:交换分区的大小。
- SwapFree: 交换分区空闲的大小。
- Dirty: 脏页的数量。
- Writeback: 正在回写的也买你数量。
- AnonPages:有反向映射的页面,通常是匿名页面并且被映射到用户空间的。
- Mapped:所有映射到用户地址空间的内容缓存页面。
- Shmem: 共享内存(tmpfs事先的shmem/devtmpfs等)。
- KReclaimable: 内核可回收内存,包括可回收的slab和其他可回收的内核页面。
- Slab:所有slab页面,包括可回收和不可回收。
- SReclaimable:可回收的slab页面。
- SUnreclaim: 不可回收的slab页面。
- KernelStack: 所有进程的内核栈总大小。
- PageTables: 用于存储页表的页面数量。
- NFS_Unstable:NFS中,发给服务器但是还没有写入磁盘的页面。
- Bounce: 针对智能访问低端内存的设备,当DMA分配到高端内存时,分配要给低端临时buffer用于复制处理。
- WritebackTmp:回写过程中使用的临时缓存
- CommitLimit:
- Committed_AS:
- VmallocTotal:vmalloc区域总大小。
- VmallocUsed:vmalloc区域使用的内存大小。
- VmallocChunk:vmalloc可用的连续最大块大小。
- Percpu:percpu机制使用的页面。
- CmaTotal:CMA机制使用的总内存。
- CmaFree:CMA机制剩余空用内存。
1.Linux内核的内存用了多少?Slab+VmallocUsed+PageTables+KernelStack+Bounce。
2.用户内存用了多少内存?
(1)LRU视角:active+inactive+unevicatable = 5104+31432+0= 36536KB
(2)缓存视角(swapcache=0):Cached+Buffers +AnonPages= 20352+436+15792=36580KB,cached和buffers内存并不是都使用完成了,这种统计方式往往大于实际使用内存。
观察内存泄露的时候重点看:Memfree、Slab的变化。
/proc/zoneinfo
zoneinfo显示了当前系统所有内存管理区的信息,可以分为以下几个部分。
1.当前内存节点的内存统计信息
Node 0,zone  DMA:第0个节点,DMA区域的总体信息。

2.当前内存管理区的总信息

节点0,zone区域。
– pages free:内存管理区中空闲的页面数量。
– min:警戒水位的页面数量。
– low:低水位的页面数量。
– high:高水位的页面数量。
– spanned:内存管理区总的页面数量,包含空洞。
– present:内存管理区总的可用页面数量,不包含空洞。
– managed:被伙伴系统管理的页面数量。
– protection:管理区中预留内存的页面数量。分别是预留给DMA,DMA32,NORMAL,HIGH。
3.每个CPU内存分配器的信息per_cpu_pageset

pagesets:表示每个CPU内存分配器中每个CPU缓存的页面信息。
– count: 在该CPU内存区域上已经分配的页面数量。
– high:页面回收的空闲页面数量的水位线,如果cpu上的页面数量超过该值,需要退还给zone。
– batch:如果缓存中没有页面了,一次性中zone中获取batch个页面。。
– vm stats threshold: 某个进程虚拟内存使用量超过该阈值时,内核将在/proc/PID/smaps打印进程的详细内存映射信息。
zoneinfo节点重点可以看一下各区域min/low/high的水位值,可以通过/proc/sys/vm/min_free_kbytes调整min值。
用户进程占用内存
/proc/pid/status
# cat /proc/1151/status 
Name:   wifi_daemon
Umask:  0022
State:  S (sleeping)
Tgid:   1151
Ngid:   0
Pid:    1151
PPid:   1
TracerPid:      0
Uid:    0       0       0       0
Gid:    0       0       0       0
FDSize: 64
Groups:  
VmPeak:   239316 kB
VmSize:   239316 kB
VmLck:         0 kB
VmPin:         0 kB
VmHWM:      1172 kB
VmRSS:      1172 kB
RssAnon:             532 kB
RssFile:             640 kB
RssShmem:              0 kB
VmData:    33540 kB
VmStk:       132 kB
VmExe:        36 kB
VmLib:      7560 kB
VmPTE:        76 kB
VmSwap:        0 kB
CoreDumping:    0
THP_enabled:    0
Threads:        3
SigQ:   0/7639
SigPnd: 0000000000000000
ShdPnd: 0000000000000000
SigBlk: 0000000000000000
SigIgn: 0000000000001000
SigCgt: 0000000180000000
CapInh: 0000000000000000
CapPrm: 000001ffffffffff
CapEff: 000001ffffffffff
CapBnd: 000001ffffffffff
CapAmb: 0000000000000000
NoNewPrivs:     0
Seccomp:        0
Seccomp_filters:        0
Speculation_Store_Bypass:       not vulnerable
SpeculationIndirectBranch:      unknown
Cpus_allowed:   ff
Cpus_allowed_list:      0-7
voluntary_ctxt_switches:        12
nonvoluntary_ctxt_switches:     0
- VmPeak: 进程使用的最大虚拟内存,通常等于进程内存描述符号mm->total_vm。
- VmSize:进程使用的虚拟内存,等于mm->total_vm。
- VmLck:记录所有用户或内核锁定的内存,主要是mlock的内存,系统回收内存时,不会优先回收这部分内存。
- VmPin:进程固定在内存的虚拟地址空间大小,记录了无法被换出到磁盘的页面数量。
- VmHWM:进程使用的最大物理内存,包括进程使用的匿名页面、文件映射页面以及共享内存页面大小总和。
- VmRSS: 进程使用的最大物理内存,通常等于VmHMM。
- RssAnon: 进程使用的匿名页面大小。
- RssFile: 进程使用的文件页面大小。
- RssShmem: 进程使用的共享内存页面大小。
- VmData:进程私有数据段占用内存大小。
- VmStk:进程用户栈占用内存大小。
- VmExe:进程代码段占用大小。
- VmLib:进程共享库占用大小。
- VmPTE:进程占用的页表大小。
- VmSwap: 进程使用巨型页的大小。
/proc/pid/smaps
7fac964000-7fac9b8000 r-xp 00000000 b3:07 403    /lib/libwifimg-v2.0.so
Size:                336 kB
KernelPageSize:        4 kB
MMUPageSize:           4 kB
Rss:                 240 kB
Pss:                 240 kB
Shared_Clean:          0 kB
Shared_Dirty:          0 kB
Private_Clean:       240 kB
Private_Dirty:         0 kB
Referenced:          136 kB
Anonymous:             0 kB
LazyFree:              0 kB
AnonHugePages:         0 kB
ShmemPmdMapped:        0 kB
FilePmdMapped:         0 kB
Shared_Hugetlb:        0 kB
Private_Hugetlb:       0 kB
Swap:                  0 kB
SwapPss:               0 kB
Locked:                0 kB
THPeligible:    0
VmFlags: rd ex mr mw me 
- 7fac964000-7fac9b8000 r-xp 00000000 b3:07 403 /lib/libwifimg-v2.0.so: 7fac964000-7fac9b8000虚拟内存段的开始和结束位置,表示一个VMA。 r-xp表示该VMA是可读、可执行、私有。00000000虚拟内存段其实地址对应映射文件中以页为单位的偏移量。
- size:虚拟内存空间大小。不是实际物理内存的分配大小,对应的是VMA的内存大小,内存总是会延迟分配。
- Rss:实际分配的内存,包括其他进程的共享内存Rss=Shared_Clean+Shared_Dirty+Private_Clean+Private_Dirty。
- Pss:平摊计算后的实际物理内存使用。共享部分按比例均分+Private_xx部分。
- Private_Dirty:进程独占的脏页面大小。
- Private_Clean:进程独占干净页面大小。
- USS:等于Private_Dirty+Private_Clean,通常用来表示进程独占的物理内存大小,去掉与其他共享内存部分。
Rss计算:
cat /proc/pid/smaps | awk '/^Rss/ {sum += $2} END {print sum}'
Pss计算
cat /proc/pid/smaps | awk '/^Pss/ {sum += $2} END {print sum}'
Uss计算
cat /proc/pid/smaps | awk '/^Pss/ {sum += $2} END {print sum}'
进程1001:
VSS=1+2+3
RSS=4+5+6
PSS=4/2+5+6
USS=5+6
看进程是否有内存泄露,可以优先看USS(Private_Dirty+Private_Clean)是否有增长。
pmap -x [pid]


– Address:虚拟地址起始地址
– Kbytes:内存块占用虚拟内存大小,单位KB。
– PSS:进程使用的物理内存大小(贡献内存按比例分配的大小),单位KB。
– Dirty:脏内存大小,指进程修改过的页面大小,单位KB。
– Swap:被交换到磁盘上的内存大小,单位KB。
– Mode:显示内存段的权限属性。
– Mapping:显示内存段对应的文件或库名。
– Total:汇总内存区域的虚拟内存大小,单位KB。
在linux物理内存中,每个页面有一个dirty的标志,如果该页面被改写了,我们称之位dirty page。总的来说,所有非dirty page的物理页面都可以被回收。进程中各个段的dirty page情况。
内核占用内存
/proc/meminfo
内核占用内存:Slab+VmallocUsed+PageTables+KernelStack+Bounce。
/proc/pagetypeinfo
Page block order:10,支持最高阶order,这里是10,表示内存大小一块为2^10页面。
Pages per block:最高阶一块内存需要的页面数量,等于2^10=1024,即page block大小为1024*4K=4MB。
Unmovable order=1的数量有87个,也就是2^1个页面组成的内存块有87个。
Movable 454:表示在DMA区域,movable类型的page block的数量为454。(按照最高阶计算oder = 2^10的页面组成的内存块)。
小结
内存统计
#!/bin/sh
while true; do
    #1计算系统总共内存及剩余内存
    TOTAL_MEM=$(grep MemTotal /proc/meminfo | awk '{print $2}')
    FREE_MEM=$(grep MemFree /proc/meminfo | awk '{print $2}')
    #2计算内核占用内存
    SLAB=$(grep Slab /proc/meminfo | awk '{print $2}')
    VMALLOC=$(grep VmallocUsed /proc/meminfo | awk '{print $2}')
    PAGETABLE=$(grep PageTables /proc/meminfo | awk '{print $2}')
    KERNELSTACK=$(grep KernelStack /proc/meminfo | awk '{print $2}')
    KERNEL_MEM=$((SLAB + VMALLOC + PAGETABLE + KERNELSTACK))
#3计算用户USS/PSS/RSS占用内存
    CURRENT_USER=$(id -u)
    TOTAL_USS=0
    TOTAL_PSS=0
    TOTAL_RSS=0
    for pid in $(ls /proc/ | grep "^[0-9]*$"); do
        if [ -f "/proc/${pid}/status" ]; then
            username=$(awk '/^Uid:/{printf $2}' "/proc/${pid}/status")
            if [ "$username" = "$CURRENT_USER" ]; then
                name=$(awk '/^Name:/{print $2}' "/proc/${pid}/status")
                mem1=0
                mem1=$(cat /proc/${pid}/smaps | awk '/^Private/ {sum += $2} END {print sum}')
                mem2=0
                mem2=$(cat /proc/${pid}/smaps | awk '/^Pss/ {sum += $2} END {print sum}')
                mem3=0
                mem3=$(cat /proc/${pid}/smaps | awk '/^Rss/ {sum += $2} END {print sum}')
            fi
            TOTAL_USS=$((TOTAL_USS + mem1))
            TOTAL_PSS=$((TOTAL_PSS + mem2))
            TOTAL_RSS=$((TOTAL_RSS + mem3))
        fi
    done
    echo "total_mem:$TOTAL_MEM KB, free_mem:$FREE_MEM KB"
    echo "slab:$SLAB KB, vmalloc:$VMALLOC KB, pagetable:$PAGETABLE KB, kernel_stack:$KERNELSTACK KB"
    echo  "Kernel_mem:$KERNEL_MEM KB, USS:$TOTAL_USS KB, PSS:$TOTAL_PSS KB, RSS:$TOTAL_RSS KB"
    sleep 1
done
查询占用较多内存的进程
#!/bin/sh
CURRENT_USER=$(id -u)
TOTAL_USS=0
TOTAL_PSS=0
TOTAL_RSS=0
for pid in $(ls /proc/ | grep "^[0-9]*$"); do
    if [ -f "/proc/${pid}/status" ]; then
        username=$(awk '/^Uid:/{printf $2}' "/proc/${pid}/status")
        if [ "$username" = "$CURRENT_USER" ]; then
            name=$(awk '/^Name:/{print $2}' "/proc/${pid}/status")
            mem1=0
            mem1=$(cat /proc/${pid}/smaps | awk '/^Private/ {sum += $2} END {print sum}')
            mem2=0
            mem2=$(cat /proc/${pid}/smaps | awk '/^Pss/ {sum += $2} END {print sum}')
          mem3=0
            mem3=$(cat /proc/${pid}/smaps | awk '/^Rss/ {sum += $2} END {print sum}')
            echo "$pid USS:$mem1, PSS:$mem2, RSS:$mem3"
         fi
fi
done
进程内存监测
#!/bin/sh
count=0
pid=$1
while true; do
 let "count++"
 #计算USS,RSS,PSS
 mem1=$(cat /proc/$pid/smaps | awk '/^Private/ {sum += $2} END {print sum}')
 mem2=$(cat /proc/$pid/smaps | awk '/^Rss/ {sum += $2} END {print sum}')
 mem3=$(cat /proc/$pid/smaps | awk '/^Pss/ {sum += $2} END {print sum}')
 #计算Dirty,进程虚拟内存
 Dirty=$(pmap -x $pid | awk '/^total/{dirty=$4}END{print dirty}')
 VSS=$(pmap -x $pid | awk '/^total/{VSS=$2}END{print VSS}')
 #计算系统使用内存,剩余内存
 used=$(free | awk '/^Mem/{used=$3}END{print used}')
 free=$(free | awk '/^Mem/{free=$4}END{print free}')
 #计算堆空间虚拟内存
start_addr=$(cat /proc/$pid/maps | grep "\\[heap\\]" | awk '{print $1}' | cut -d'-' -f1)
end_addr=$(cat /proc/$pid/maps | grep "\\[heap\\]" | awk '{print $1}' | cut -d'-' -f2)
start_addr=$(printf "%d" "0x$start_addr")
end_addr=$(printf "%d" "0x$end_addr")
heap_vs=$(expr $end_addr - $start_addr)
heap_vs=$(expr $heap_vs / 1024)
 #计算栈空间虚拟内存
start_addr=$(cat /proc/$pid/maps | grep "\\[stack\\]" | awk '{print $1}' | cut -d'-' -f1)
end_addr=$(cat /proc/$pid/maps | grep "\\[stack\\]" | awk '{print $1}' | cut -d'-' -f2)
start_addr=$(printf "%d" "0x$start_addr")
end_addr=$(printf "%d" "0x$end_addr")
stack_vs=$(expr $end_addr - $start_addr)
stack_vs=$(expr $stack_vs / 1024)
 echo "count:$count,USS:$mem1 KB,VSS:$VSS KB, RSS:$mem2 KB,PSS:$mem3 KB,Dirty:$Dirty KB,heapvs:$heap_vs KB,stackvs:$stack_vs KB,used:$used KB,free:$free KB"
 sleep 1
done
测试代码,可配合脚本观察
int main(int argc, char *argv[])
{
    char *ptr1;
    char *ptr2;
    char *ptr3;
    char *ptr4;
    char *ptr5;
    struct mallinfo m_info;
    int size_kb = 65;
    getchar();
    mallopt(M_TRIM_THRESHOLD, 1024 * 128);
    printf("malloc ptr[0] %dkb\\n", size_kb);
    ptr1 = (char *)malloc(1048 * size_kb);
    //memset(ptr1,24,1048 * size_kb); //memset不会导致USS增加,可能是由于值是一样的.
    //memset(ptr1,25,1048 * size_kb); 即使用两次memset设置不同值也不会增加USS
    for (int i = 0; i < 1048 * size_kb; i++) {
        ptr1[i] = i % 255;
    }
    getchar();
    printf("malloc ptr[1] %dkb\\n", size_kb);
    ptr2 = (char *)malloc(1048 * size_kb);
    //memset(ptr2,25,1048 * size_kb);
    for (int i = 0; i < 1048 * size_kb; i++) {
        ptr2[i] = i % 255;
    }
    getchar();
    printf("malloc ptr[2] %dkb\\n", size_kb);
    ptr3 = (char *)malloc(1048 * size_kb);
    //memset(ptr3,26,1048 * size_kb);
    for (int i = 0; i < 1048 * size_kb; i++) {
        ptr3[i] = i % 255;
    }
getchar();
    printf("malloc ptr[3] %dkb\\n", size_kb);
    ptr4 = (char *)malloc(1048 * size_kb);
    //memset(ptr4,27,1048 * size_kb);
    for (int i = 0; i < 1048 * size_kb; i++) {
        ptr4[i] = i % 255;
    }
    getchar();
    printf("malloc ptr[4] %dkb\\n", size_kb);
    ptr5 = (char *)malloc(1048 * size_kb);
    //memset(ptr5,28,1048 * size_kb);
    for (int i = 0; i < 1048 * size_kb; i++) {
        ptr5[i] = i % 255;
    }
    getchar();
    printf("free ptr[0] %dkb\\n", size_kb);
    free(ptr1);
    getchar();
    printf("free ptr[1] %dkb\\n", size_kb);
    free(ptr2);
    getchar();
    printf("free ptr[2] %dkb\\n", size_kb);
    free(ptr3);
    getchar();
    printf("free ptr[3] %dkb\\n", size_kb);
    free(ptr4);
    getchar();
    printf("free ptr[4] %dkb\\n", size_kb);
    free(ptr5);
    getchar();
    return 0;
}
操作系统对于小块内存的管理方式,如 Linux 内核中的延迟映射(Lazy Mapping)或零页复制(Zero Page COW)等技术所导致的。在某些情况下,操作系统可能会推迟实际的物理页面映射,直到首次访问相应的内存位置。这意味着即使您访问和修改了分配的内存,实际的物理页面映射可能仍然被推迟。即使使用memset修改了内存,但是可能也不会进行映射,通常可能只有在内存被修改为不同内容时,才会进行实际物理页面映射。而只有做了物理页面映射,进程的USS才会增加。

