在进程的眼里,所有的内存都是虚拟内存,但是这些内存所对应的物理内存是多少呢?正如我们前面介绍的那样,并不是每块虚拟内存都有对应的物理内存,可能对应的数据在磁盘上的一个文件中,或者交换空间上的一块区域内。一个进程真正的物理内存使用情况只有内核知道,我们只有通过内核开发的一些接口来获取这些统计数据。
注意: 一般top使用的单位是KB, 直接除以1000就是M, 小数点前进3位; top倾向于看每个进程的内存使用情况,FREE倾向于看整个系统的内存使用情况,pmap看的是具体内存的使用情况。

TOP

先看看top的输出,top所用到的数据来自于/prod/pid/stam, 这里只是摘录了几条数据:

  1. PID USER PR NI VIRT RES SHR S %CPU %MEM TIME+ COMMAND
  2. 2530 root 20 0 0 0 0 S 0.3 0.0 0:02.69 kworker/0:0
  3. 2714 dev 20 0 41824 3700 3084 R 0.3 0.7 0:00.02 top
  4. 3008 dev 20 0 22464 5124 3356 S 0.0 1.0 0:00.02 bash
  • VIRT: 进程所使用的虚拟内存的大小
  • RES: 系统为虚拟空间分配的物理内存的大小,包括file backed和anonymous内存,其中anonymous包含了进程自己分配和使用的内存,以及和别的进程通过mmap共享的内存;而file backed的内存就是加载可执行文件和动态库所占用的内存,以及通过private的方式调用mmap映射文件所使用的内存(当在内存中修改了这部分数据并且没有写回文件,那么这部分内存就变成了anonymous),这部分内存也可能跟别的进程共享。
  • SHR: RES的一部分,表示和别的进程共享的内存,包括通过mmap共享内存和file backed的内存。当通过prive方式调用mmap映射一个文件时,如果没有修改文件内容,那么这部分内存就是anonymous并且非SHR的。
  • %MEM:等于RES/total * 100%, 这里total指总的物理内存的大小。然而需要注意的是,由于SHR会被多个进程所共享,所以系统中所有进程的RES加起来可能会超过物理内存的总和。由于同样的原因,所有进程的%MEM总和可能超过100%。
  • 从上面的分析可以看出,VIRT的参考意义不大,它只能反应出程序的大小,而RES也不能完全代表一个进程真正占用的内存,因为它里面还包含了SHR的部分。比如三个bash进程共享一个libc动态库,那么libc所占用的内存算谁的呢?如果启动一个bash占用了4M内存的RES, 其中3M是libc占用的。由于三个进程都共享那3M libc, 那么启动3个bash实际占用的内存是3 * (4-3)+ 3 = 6M。但是如果单纯的按照RES来算的话,三个进程就用了12M的空间,所以理解RES和SHR这两个数据的含义对我们评判一个服务的状态尤其重要
  • 最后,还有以一个很关键的点!那就是top命令中RES和pmap输出的RSS是用一个东西!

FREE

使用free命令可以查看当前内存的使用情况:

  1. [root@localhost ~]$ free
  2. total used free shared buffers cached
  3. Mem: 264420684 213853512 50567172 71822688 2095364 175733516
  4. -/+ buffers/cache: 36024632 228396052
  5. Swap: 16777212 1277964 15499248
  • mem: 物理内存
  • swap: 交换内存,即是可以把数据放在硬盘上的数据
  • shared: 共享内存,存在物理内存中
  • buffers: 用于存放在输出到disk(块设备)的数据
  • cached: 存放从disk上读出的数据
  • free: 空闲的物理内存

在这里,我们还可以做一个映射关系:

  1. [root@localhost ~]$ free
  2. total used free shared buffers cached
  3. Mem: total_mem used_mem free_mem shared_mem buffer cache
  4. -/+ buffers/cache: real_used real_free
  5. Swap: total_swap used_swap free_swap

一般认为,buffer和cache表示可以再进行利用的内存,所以在计算空闲内存时,会将其剔除,需要记住这几个等式:

  1. real_used = used_mem - buffer - cache
  2. real_free = free_mem + buffer + cache
  3. total_mem = used_mem + free_mem

docker中内存监控

说完linux的内存,我们再来看一下docker的内存监控,docker自身提供了一种内存监控的方式,即可以通过docker stat对容器内存进行监控。该方式实际是通过对cgroup中相关数据进行取值从而计算得到。

cgroup

cgroup到底是啥?这个gourp代表了什么?

cgroup中的memory子系统为hierarchy提供了如下文件:

  1. [root@localhost ~]$ ll /cgroup/memory/docker/53a11f13c08099dd6d21030dd2ddade54d5cdd7ae7e9e68f5ba055ad28498b6f/
  2. 总用量 0
  3. --w--w--w- 1 root root 0 2 22 12:51 cgroup.event_control
  4. -rw-r--r-- 1 root root 0 5 25 17:07 cgroup.procs
  5. -rw-r--r-- 1 root root 0 2 22 12:51 memory.failcnt
  6. --w------- 1 root root 0 2 22 12:51 memory.force_empty
  7. -rw-r--r-- 1 root root 0 3 30 17:06 memory.limit_in_bytes
  8. -rw-r--r-- 1 root root 0 2 22 12:51 memory.max_usage_in_bytes
  9. -rw-r--r-- 1 root root 0 2 22 12:51 memory.memsw.failcnt
  10. -rw-r--r-- 1 root root 0 3 30 17:06 memory.memsw.limit_in_bytes
  11. -rw-r--r-- 1 root root 0 2 22 12:51 memory.memsw.max_usage_in_bytes
  12. -r--r--r-- 1 root root 0 2 22 12:51 memory.memsw.usage_in_bytes
  13. -rw-r--r-- 1 root root 0 2 22 12:51 memory.move_charge_at_immigrate
  14. -rw-r--r-- 1 root root 0 2 22 12:51 memory.oom_control
  15. -rw-r--r-- 1 root root 0 3 30 17:06 memory.soft_limit_in_bytes
  16. -r--r--r-- 1 root root 0 2 22 12:51 memory.stat
  17. -rw-r--r-- 1 root root 0 2 22 12:51 memory.swappiness
  18. -r--r--r-- 1 root root 0 2 22 12:51 memory.usage_in_bytes
  19. -rw-r--r-- 1 root root 0 2 22 12:51 memory.use_hierarchy
  20. -rw-r--r-- 1 root root 0 2 22 12:51 notify_on_release
  21. -rw-r--r-- 1 root root 0 2 22 12:51 tasks

我们主要介绍几个和docker监控相关的文件:

  1. memory.usage_in_bytes 已使用的内存量(包含cachebuffer)(字节),相当于linuxused_meme
  2. memory.limit_in_bytes 限制的内存总量(字节),相当于linuxtotal_mem
  3. memory.failcnt 申请内存失败次数计数
  4. memory.memsw.usage_in_bytes 已使用的内存和swap(字节)
  5. memory.memsw.limit_in_bytes 限制的内存和swap容量(字节)
  6. memory.memsw.failcnt 申请内存和swap失败次数计数
  7. memory.stat 内存相关状态

memory.stat

  1. cache 页缓存,包括 tmpfsshmem),单位为字节
  2. rss 匿名和 swap 缓存,不包括 tmpfsshmem),单位为字节
  3. mapped_file memory-mapped 映射的文件大小,包括 tmpfsshmem),单位为字节
  4. pgpgin 存入内存中的页数
  5. pgpgout 从内存中读出的页数
  6. swap swap 用量,单位为字节
  7. active_anon 在活跃的最近最少使用(least-recently-usedLRU)列表中的匿名和 swap 缓存,包括 tmpfsshmem),单位为字节
  8. inactive_anon 不活跃的 LRU 列表中的匿名和 swap 缓存,包括 tmpfsshmem),单位为字节
  9. active_file 活跃 LRU 列表中的 file-backed 内存,以字节为单位
  10. inactive_file 不活跃 LRU 列表中的 file-backed 内存,以字节为单位
  11. unevictable 无法再生的内存,以字节为单位
  12. hierarchical_memory_limit 包含 memory cgroup 的层级的内存限制,单位为字节
  13. hierarchical_memsw_limit 包含 memory cgroup 的层级的内存加 swap 限制,单位为字节

docker 中内存计算方式

计算方式是在统计内存使用量的时候将cache计算排除出去,类似于linux中计算read_used时将buffer和cache排除一样。

cache并不能直接应用memory.stat中的cache, 因为其中包括了tmpfs(即share memory, 共享内存), 而tmpfs算是实际使用的内存部分。

在memory.stat中存在有:

  1. active_file + inactive_file = cache - size of tmpfs

因此可以计算实际使用的内存量为:

  1. real_used = memory.usage_in_bytes - (rss + active_file + inactive_file)

参考

Linux进程的内存使用情况
Docker容器内存监控(好文章,解释了我关于docker的许多疑惑)