Linux内存解析

系统内存

我们在查看系统内存使用状况时,经常会常用free命令。下面是在一台centos物理机中执行free后的输出:

# free
         total      used       free    shared    buff/cache    available     
Mem: 131635324  12276220  104836264     17600      14522840    117816640
Swap:        0         0          0

输出包括两行:Mem和Swap。其中Mem表示系统中实际的物理内存;Swap表示交换分区(类似windows中的虚拟内存),是硬盘中一个独立的分区,用于在系统内存不够时,临时存储被释放的内存空间中的内容。

上面的前三列"total"、“used”、"free"分别表示总量,使用量和有多少空闲空间。

对于Swap来说,三者的关系很简单,就是total = used + free。(在上面的例子中,由于这台主机禁用swap空间,所以swap的值都是0。)

对于Memory来说,我们发现total = used + free + buff/cache。其中buff和cache分别表示存放了将要写到磁盘中的数据从磁盘的读取的数据的内存。也就是说内存除了存储进程运行所需的运行时数据之外,还为了提高性能,缓存了一部分I/O数据。

由于系统的cache和buffer可以被回收,所以可用的(available)内存比空闲的(free)要大。在部署了某些高I/O应用的主机中,available会比free看起来大很多,这是由于大量的内存空间用于缓存对磁盘的I/O数据。例如下面是在一台kafka服务器上执行free的结果(-g表示单位为GB):

# free -g
        total    used    free    shared    buff/cache    available
Mem:      125      38      10         0            77           84

free命令的所有输出值都是从/proc/meminfo中读出的

进程内存

通过free命令,我们可以看到系统总体的内存使用状况。但很多时需要查看特定进程(process)所消耗的内存,这时我们最常用的命令是ps,这个命令的输出中有两列与内存相关,分别是VSZRSS。此外还有另外两个类似的指标——PSSUSS,这里将这四个一并讨论:

缩写全称含义
VSZ(VSS)Virtual Memory Size虚拟内存大小,表明了该进程可以访问的所有内存,包括被交换的内存和共享库内存。VSZ对于判断一个进程实际占用的内存并没有什么帮助。
RSSResident Set Size常驻内存集合大小,表示相应进程在RAM中占用了多少内存,并不包含在Swap中占用的虚拟内存。包括进程所使用的共享库所占用的全部内存,即使某个共享库只在内存中加载了一次,所有使用了它的进程的RSS都会分别把它计算在内。(把所有进程的RSS加到一起,通常会比实际使用的内存大,因为一些共享库所用的内存被重复计算了多次。)
PSSProportional Set Size与RSS类似,唯一的区别是在计算共享库内存是是按比例分配。比如某个共享库占用了3兆内存,同时被3个进程共享,那么在计算这3个进程的PSS时,该共享库这会贡献1兆内存。
USSUnique Set Size进程独自占用的物理内存,不包含Swap和共享库。

一幅图胜过千言万语:
在这里插入图片描述
可以看到,这四者的大小关系是:VSS >= RSS >= PSS >= USS

进程的内存使用情况可以从/proc/PID/status中读取,比如

  • VmSize: 当前的Virtual Memory Size
  • VmRSS: Resident Set Size
  • VmLib:进程所加载的动态库所占用的内存大小

容器内存

接下来看一下在docker中如何获得容器的内存使用状况。容器是系统内核所提供的一种进程级虚拟化机制,实现了各个进程间的资源隔离。

一个最自然的想法就是进入一个容器内部,执行free命令:

# free -g
        total    used    free    shared    buff/cache    available
Mem:      125      38      10         0            77           84

结果输出的是宿主机内存信息。显然这样不行。

查看容器中的主进程(PID = 1)的/proc/1/status,显示的内容包括进程的VSS和RSS,这个没有问题。

在遵循one docker one process的原生容器中,主进程基本反应了容器的内存使用状况,但这毕竟不完整,况且一个容器中运行多个进程的情况也很常见。因而需要一种更优雅的容器内存查看方式,那就是cgroup。

cgroup是Linux内核提供的一种限制和查询一组进程资源使用的功能,docker用它实现了对容器可用资源的限制。一个容器的某类资源(例如:内存)对应系统中一个cgroup子系统(subsystem) hierachy中的节点(例如/sys/fs/cgroup/memory/docker下的子目录)。在这个目录中有很多文件提供了容器对系统资源使用状况的信息。比如:memory.stat文件包含了容器的内存状况:

# cat memory.stat
cache 12288
rss 57253888
rss_huge 8388608
mapped_file 0
swap 0
pgpgin 44462
pgpgout 39168
pgfault 77640
pgmajfault 0
inactive_anon 0
active_anon 57253888
inactive_file 0
active_file 12288
unevictable 0
hierarchical_memory_limit 9223372036854771712
hierarchical_memsw_limit 9223372036854771712
total_cache 12288
total_rss 57253888
total_rss_huge 8388608
total_mapped_file 0
total_swap 0
total_pgpgin 44462
total_pgpgout 39168
total_pgfault 77640
total_pgmajfault 0
total_inactive_anon 0
total_active_anon 57253888
total_inactive_file 0
total_active_file 12288
total_unevictable 0

在上面的文件中,重点关注cache和rss

  • cache - # of bytes of page cache memory.
  • rss - # of bytes of anonymous and swap cache memory (includes
    transparent hugepages).

在Linux内核中,对于进程的内存使用与Cgroup的内存使用统计有一些相同和不同的地方:

  • 进程的RSS为进程使用的所有物理内存,不包含Swap,包含共享内存;cgroup RSS包含Swap,不包含共享内存。(因而,在没有swap的情况下,cgroup的RSS更像是进程的USS)
  • 两者都不包含文件cache。
  • cgroup cache包含文件cache和共享内存。

Google的容器内存监控工具cAdvisor就是通过读取cgroup信息来获取容器的内存使用信息。其中有一个监控指标container_memory_usage_bytes(Current memory usage in bytes, including all memory regardless of when it was accessed),如果只看名字和注解,很自然就认为它是容器所使用的内存。但是你可能会发现,如果不设置内存上限,某些容器的这个值出奇的大。原因就在于这里所谓的usage和通过free指令输出的used memory的含义是不同的,前者实际上是cgroup的rss和cache的和,而后者不包含cache。

结论:
就像进程的USS最准确的反映了进程自身使用的内存,cgroup的RSS也最真实的反映了容器所占用的内存空间。

Logo

更多推荐