Linux 监控之 Memory

2013-04-06 kernel linux

在 Linux 的内存分配机制中,优先使用物理内存,当物理内存还有空闲时,不会释放其占用内存,就算占用内存的程序已经被关闭了,该程序所占用的内存用来做缓存使用,这样对于开启过的程序、或是读取刚存取过得数据会比较快,可以提高整体 IO 效率。

free 命令

内存使用情况可以通过 free 命令查看,在 CentOS 中,是 procps-ng 包中的一个程序,该命令实际是读取 /proc/meminfo 文件,对应内核源码中的 fs/proc/meminfo.c。

$ free
         total       used       free     shared     buffers      cached
Mem:    386024     377116       8908          0       21280      155468
-/+ buffers/cache: 200368     185656
Swap:    393552        0      393552

最新版本的 free 会将 buffer/cache 展示在一起,可以通过 free -w 分开查看 buffers 以及 cache;默认的单位是 KB 。

Mem 表示物理内存统计
    total  : 物理内存总大小;
    used   : 已经分配的内存总计(含buffers 与cache ),但其中部分可能尚未使用;
    free   : 未被分配的内存;
    shared : 多个进程共享的内存总额;
    buffers: 系统分配但未被使用的 buffers 数量,磁盘buffers数量;
    cached: 系统分配但未被使用的 cache 数量,磁盘cache数量;

-/+ buffers/cached 表示物理内存的缓存统计
    used: 实际使用的 buffers 与 cache 总量(used-buffers-cached),也是实际使用的内存总量;
    free: 未被使用的 buffers 与 cache 和未被分配的内存之和(buffers+cached+free),这就是系统当前实际可用内存;

Swap 表示硬盘上交换分区的使用情况
    只有 mem 被当前进程实际占用完,即没有了 buffers 和 cache 时,才会使用到 swap 。

其实第一行可以从操作系统的角度去看,即 OS 认为自己总共有 total 的内存,分配了 used 的内存,还有 free 的内存;其中分配的内存中分别有 buffers 和 cached 的内存还没有使用,也就是说 OS 认为还有空闲的内存,又懒得收回之前分配,但已经分配的不能再使用了。

第二行可以认为从进程的角度去看,可用的内存包括了空闲的+buffers+cached,total-空闲的就是时用的内存数。

可以通过如下方式计算:

----- 实际可用内存大小
Free(-/+ buffers/cache) = Free(Mem) + buffers(Mem) + Cached(Mem)
                 185656 =      8908 +        21280 +      155468

----- 物理内存总大小
total(Mem) = used(-/+ buffers/cache) + free(-/+ buffers/cache)
    386024 =                  200368 +                  185656
           = used(Mem) + free(Mem)
           =    377116 +      8908

----- 已经分配的内存大小
Used(Mem) = Used(-/+ buffers/cache) + buffers(Mem) + Cached(Mem)
   377116 =                  200368 +        21280 +     155468

关于内存的其它信息可以参考如下文件。

/proc/meminfo                  // 机器的内存使用信息
/proc/pid/maps                 // 显示进程 PID 所占用的虚拟地址
/proc/pid/statm                // 进程所占用的内存
/proc/self/statm               // 当前进程的信息

新版

如上所述,新版的命令行输入如下。

$ free -w
              total        used        free      shared     buffers       cache   available
Mem:        8070604     2928928      495012     1162676      104156     4542508     3624840
Swap:       8127484      232388     7895096

total     (MemTotal     @ /proc/meminfo)
free      (MemFree      @ /proc/meminfo)
shared    (Shmem        @ /proc/meminfo)  主要是tmpfs使用
buffers   (Buffers      @ /proc/meminfo)
cache     (Cached+Slab  @ /proc/meminfo)
available (MemAvailable @ /proc/meminfo)
used = total - free - buffers - cache     (注意,没有去除shared)

可以通过 free -k -w && cat /proc/meminfo 命令进行测试。

Swap对性能的影响

当内存不足的时候把磁盘的部分空间当作虚拟内存来使用,而实际上并非是在内存不足的时候才使用,有个参数叫做 swappiness,是用来控制 swap 分区使用的,可以直接查看 /proc/sys/vm/swappiness 文件。

默认值是 60,范围是 [0, 100];为 0 表示会最大限度使用物理内存,然后才是 swap;为 100 时,表示尽量使用 swap 分区,把内存上的数据及时的搬运到 swap 空间里面。

分配太多的 Swap 空间会浪费磁盘空间,而 Swap 空间太少,则系统有可能会发生错误。当系统的物理内存用光了,系统就会跑得很慢,但仍能运行;但是如果 Swap 空间也用光了,那么系统就会发生错误。

通常情况下,Swap 空间是物理内存的 2~2.5 倍,最小为 64M;这也根据不同的应用,有不同的配置:如果是桌面系统,则只需要较小的 Swap 空间;而大的服务器则视情况不同需要不同大小的 Swap 空间,一般来说对于 4G 以下的物理内存,配置 2 倍的 swap ,4G 以上配置 1 倍。

另外, Swap 分区的数量对性能也有很大的影响。因为 Swap 交换的操作是磁盘 IO 的操作,如果有多个 Swap 交换区, Swap 空间的分配会以轮流的方式操作于所有的 Swap ,这样会大大均衡 IO 的负载,加快 Swap 交换的速度。

实际上,当空闲物理内存不多时,不一定表示系统运行状态很差,因为内存的 cache 及 buffer 部分可以随时被重用。只有 swap 被频繁调用,才是内存资源是否紧张的依据,可以通过 vmstat 查看

状态查看

常用命令

介绍下与内存相关的命令。

pmap

可以根据进程查看进程相关信息占用的内存情况,可以通过 -x-X-XX 查看详细信息。

$ pmap -x $(pidof mysqld)
Address           Kbytes     RSS   Dirty Mode  Mapping
0000000000400000   37116    9120       0 r-x-- mysqld
0000000002a3e000     872     192      12 r---- mysqld
0000000002b18000     700     300     196 rw--- mysqld
0000000002bc7000     772     628     628 rw---   [ anon ]
00007f0df23ff000       4       0       0 -----   [ anon ]
00007f0df2400000   53248    8472    8472 rw---   [ anon ]
00007f0df5bfc000       4       0       0 -----   [ anon ]
... ...
00007fff995ca000     132      76      76 rw---   [ stack ]
00007fff995f2000       8       4       0 r-x--   [ anon ]
ffffffffff600000       4       0       0 r-x--   [ anon ]
---------------- ------- ------- -------
total kB          705216  141876  129408

输出值: 显示扩展格式
  Address : 虚拟内存开始地址;
  Kbytes  : 占用内存的字节数,单位是KB;
  RSS     : (Resident Set Size)驻留内存的字节数(KB);
  Dirty   : 脏页的字节数(包括共享和私有的)(KB);
  Mode    : 内存页的权限(r=read, w=write, x=execute, s=shared, p=private);
  Mapping : 占用内存的文件、[anon] (分配的内存)、[stack] (堆栈);

$ pmap -d $(pidof mysqld)
Address           Kbytes Mode  Offset           Device    Mapping
0000000000400000   23288 r-x-- 0000000000000000 0ca:00001 mysqld
0000000001cbe000     940 r---- 00000000016be000 0ca:00001 mysqld
0000000001da9000     680 rw--- 00000000017a9000 0ca:00001 mysqld
0000000001e53000     888 rw--- 0000000000000000 000:00000   [ anon ]
0000000001f31000   11680 rw--- 0000000000000000 000:00000   [ anon ]
00007fd784000000    4248 rw--- 0000000000000000 000:00000   [ anon ]
00007fd784426000   61288 ----- 0000000000000000 000:00000   [ anon ]
00007fd78c000000     132 rw--- 0000000000000000 000:00000   [ anon ]
00007fd78c021000   65404 ----- 0000000000000000 000:00000   [ anon ]
00007fd7d2c67000      12 rw-s- 0000000000000000 000:0000a [aio] (deleted)
00007fd7d2c6a000      12 rw-s- 0000000000000000 000:0000a [aio] (deleted)
00007fd7d2c6d000       4 rw-s- 0000000000000000 000:0000a [aio] (deleted)
00007fd7d2c6e000       4 rw--- 0000000000000000 000:00000   [ anon ]
00007fd7d2c6f000       4 r---- 000000000001f000 0ca:00001 ld-2.17.so
00007fd7d2c70000       4 rw--- 0000000000020000 0ca:00001 ld-2.17.so
00007fd7d2c71000       4 rw--- 0000000000000000 000:00000   [ anon ]
00007ffc8fef3000     132 rw--- 0000000000000000 000:00000   [ stack ]
mapped: 1248492K    writeable/private: 545308K    shared: 128K

输出值: 显示设备格式
  Offset: 文件偏移
  Device: 设备名 (major:minor)
最后一行输出:
  mapped            : 该进程映射的虚拟地址空间大小,也就是该进程预先分配的虚拟内存大小,即ps出的vsz;
  writeable/private : 进程所占用的私有地址空间大小,也就是该进程实际使用的内存大小,不含shared libraries;
  shared            : 进程和其他进程共享的内存大小;
假设上述结构导出到了/tmp/1文件中,可以通过如下命令计算:
  mapped: 所有行Kbytes字段的累加
    awk 'BEGIN{sum=0};{sum+=$2};END{print sum}' /tmp/1
  writeable/private: 模式为w+p,且不是s的内存
    awk 'BEGIN{sum=0};{if($3~/w/ && $3!~/s/) {sum+=$2}};END{print sum}' /tmp/1
  shared: 共享内存数
    awk 'BEGIN{sum=0};{if($3~/s/) {sum+=$2}};END{print sum}' /tmp/1

vmstat

vmstat 不会将自己作为一个有效的进程。

$ vmstat [options] [delay [count]]

常用选项:
  delay: 每多少秒显示一次。
  count: 显示多少次,默认为一次。
  -a: 显示活跃和非活跃内存。
  -f: 显示从系统启动之后 forks 的数量;包括了 fork, vfork, clone 的系统调用,等价于创建的全部任务数。
  -m,--slabs: 显示 slabinfo 。
  -n,--one-header: 只显示一次,而不是周期性的显示。
  -s,--stats: 显示内存统计以及各种活动信息。
  -d,--disk: 显示磁盘的相关统计。
  -D,--disk-sum: 显示硬盘统计信息的摘要。
  -p,--partition device: 显示某个磁盘的统计信息。
  -S,--unit char: 显示输出时的单位,1000(k),1024(K),1000*1000(m),1024*1024(M),默认是 K,不会改变 swap 和 block 。

----- 每秒刷新一次
$ vmstat -S M 1
procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
r  b   swpd   free   buff  cache   si   so    bi    bo   in    cs us sy id wa
3  0      0   1963    607   2359    0    0     0     0    0     1 32  0 68  0

各个段的含义为:
  procs 进程
    r: 等待运行的进程数,如果运行队列过大,表示CPU很忙,一般会造成CPU使用率很高。
    b: 不可中断睡眠(uninterruptible sleep)的进程数,也就是被阻塞的进程,通常为IO。
  memory 内存
    swpd: 虚拟内存(Virtual Memory)的使用量,应该是交换区。</li><li>
    free: 空闲内存数,同free命令中的free(Mem)。</li><li>
    buff: 同free命令,已分配未使用的内存大小。</li><li>
    cache: 同free命令,已分配未使用的内存大小。</li><li>
    inact: 非活跃(inactive)内存的大小,使用 -a 选项????。</li><li>
    active: 活跃(active)内存的大小,使用 -a 选项????。
  swap 交换区
    si: 每秒从交换区(磁盘)写到内存的大小。
    so: 每秒写入交换区(磁盘)的大小。
        如果这两个值长期大于0,系统性能会受到影响,即我们平常所说的Thrashing(颠簸)。
  IO
    bi: 每秒读取的块数(blocks/s)。
    bo: 每秒写入的块数(blocks/s)。
  system 系统
    in: 每秒的中断数,包括时钟中断。
    cs: 每秒上线文的交换数。
  CPU
    us: 用户进程执行时间。
    sy: 系统进程执行时间。
    id: 空闲进程执行时间,2.5.41 之后含有 IO-wait 的时间。
    wa: 等待 IO 的时间,2.5.41 之后含有空闲任务。

vmtouch

通过 vmtouch offical site 可以查看文件在内存中的占比,可以下载 本地源码

使用参数可以直接通过 vmtouch -h 查看,如下是简单的示例:

----- 查看/etc目录下有多少在缓存中
$ vmtouch /etc/
           Files: 2844
     Directories: 790
  Resident Pages: 274/9971  1M/38M  2.75%
         Elapsed: 0.15243 seconds

----- 查看一个大文件在缓存中的比例
$ vmtouch -v big-dataset.txt
big-dataset.txt
[                                                            ] 0/169321

           Files: 1
     Directories: 0
  Resident Pages: 0/169321  0/661M  0%
         Elapsed: 0.011822 seconds

----- 缓存部分内容到内存中,分别对应了文本和二进制文件
$ tail -n 10000 big-dataset.txt > /dev/null
$ hexdump -n 102400 big-dataset.txt > /dev/null

----- 再次查看缓存的文件,也就是62页在内存中
$ vmtouch -v big-dataset.txt
big-dataset.txt
[o                                                           ] 62/169321

           Files: 1
     Directories: 0
  Resident Pages: 62/169321  248K/661M  0.0366%
         Elapsed: 0.003463 seconds

----- 将文件加载到内存中
$ vmtouch -vt big-dataset.txt
big-dataset.txt
[OOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOOO] 169321/169321
           Files: 1
     Directories: 0
   Touched Pages: 169321 (661M)
         Elapsed: 1.7372 seconds

----- 手动从内存中删除
$ vmtouch -ve big-dataset.txt
Evicting big-dataset.txt
           Files: 1
     Directories: 0
   Evicted Pages: 42116 (164M)
         Elapsed: 0.076824 seconds

----- 将目录下的所有文件锁定到内存中
$ vmtouch -dl /var/www/htdocs/critical/

在 Linux 中,主要是通过 posix_fadvise() 来清除缓存;通过 mincore() 判断页是否存在于缓存中。

smem

Linux 使用的是虚拟内存 (virtual memory),因此要准确的计算一个进程实际使用的物理内存就比较麻烦,如下是在计算内存使用率时比较重要的指标:

  • RSS (Resident Set Size) 表示进程占用的物理内存大小,可以使用 top 命令查询;不过将各进程的 RSS 值相加,通常会超出整个系统的内存消耗,这是因为 RSS 中包含了各进程间共享的内存。
  • PSS (Proportional Set Size) 它将共享内存的大小进行平均后,再分摊到各进程上去,相比来说更准确。
  • USS (Unique Set Size) 也就是 PSS 中只属于本进程的部分,只计算了进程独自占用的内存大小,不包含任何共享的部分。

可以通过 smem 查看,这是一个 Python 写的程序,该程序也可以通过 matplotlib 输出图片;另外,同时提供了一个 smemcap.c 程序,可以打包嵌入式的相关数据,然后通过 -S/--source 指定打包文件。

在 CentOS 中,可以通过如下命令安装。

# yum --enablerepo=epel install smem python-matplotlib

进程内存使用量

简单介绍下如何查看各个进程的内存使用量。

top

可以直接使用 top 命令后,查看 %MEM 的内容,表示该进程内容使用比例,可以通过 -u 指定用户,也就是只监控特定的用户。

常用的命令有。

P: 按%CPU使用率排行
T: 按TIME+排行
M: 按%MEM排行

ps

如下例所示:

$ ps -e -o 'pid,comm,args,pcpu,rsz,vsz,stime,user,uid' | sort -nrk5

其中 rsz 为实际内存,上例实现按内存排序,由大到小。

小结

可以通过 free 查看整个系统内存的使用情况,free(Mem) 过小不会对系统的性能产生影响,实际剩余的内存数为 free(-/+ buffers/cache)。

通过 vmstat 1 3 输出的 swap(si/so) 可以查看交换区的切入/出情况,如果长期不为 0,则可能是因为内存不足,此时应该查看那个进程占用内存较多。

参考