RSS( Resident Set Size )常驻内存集合大小,表示相应进程在RAM中占用了多少内存,并不包含在SWAP中占用的虚拟内存。即使是在内存中的使用了共享库的内存大小也一并计算在内,包含了完整的在stack和heap中的内存。

VSZ (Virtual Memory Size),表明是虚拟内存大小,表明了该进程可以访问的所有内存,包括被交换的内存和共享库内存。

MADV_DONTNEED:内核会在进程的页表中将这些页标记为 “未分配”,从而进程的 RSS 就会尽快释放和变小。OS 后续可以将对应的物理页分配给其他进程。
MADV_FREE:内核只会在页表中将这些进程页面标记为可回收,在需要的时候才回收这些页面。

其中MADV_FREE是Go 1.12版本才引入的,官网上的介绍如下:


On Linux, the runtime now uses MADV_FREE to release unused memory. This is more efficient but may result in higher reported RSS. The kernel will reclaim the unused data when it is needed. To revert to the Go 1.11 behavior (MADV_DONTNEED), set the environment variable GODEBUG=madvdontneed=1.
大意就是使用MADV_FREE方式,程序内存不会立刻回收,即RSS值不会立刻下降,只有当OS内存紧缺时才会回收Go程序的内存返回给OS;而Go 1.11以及之前的版本默认采用的是 MADV_DONTNEED方式,程序RSS值下降很快。因此如果需要使程序内存占用下降很快的话,可设置环境变量GODEBUG=madvdontneed=1。
注:Linux 4.5及之后的版本中,默认使用MADV_FREE方式。

runtime/mem_linux.go源码里注释如下:
在这里插入图片描述

从1.16开始由MADV_FREE方式切换为MADV_DONTNEED方式

Go1.12 以前

Go Runtime 在 Linux 上默认使用的是 MADV_DONTNEED 策略。

  // 没有任何奇奇怪怪的判断
 madvise(v, n, _MADV_DONTNEED)

从整体效果来看,进程 RSS 可以下降的比较快,但从性能效率上来看差点。

#Go1.12-Go1.15

当前 Linux 内核版本 >=4.5 时,Go Runtime 在 Linux 上默认使用了性能更为高效的 MADV_FREE 策略。

 var advise uint32
 if debug.madvdontneed != 0 {
  advise = _MADV_DONTNEED
 } else {
  advise = atomic.Load(&adviseUnused)
 }
 if errno := madvise(v, n, int32(advise)); advise == _MADV_FREE && errno != 0 {
  // MADV_FREE was added in Linux 4.5. Fall back to MADV_DONTNEED if it is
  // not supported.
  atomic.Store(&adviseUnused, _MADV_DONTNEED)
  madvise(v, n, _MADV_DONTNEED)
 }

从整体效果来看,进程RSS 不会立刻下降,要等到系统有内存压力了才会释放占用,RSS 才会下降。

强制使用MADV_DONTNEED

GODEBUG=madvdontneed=1 go run

https://mp.weixin.qq.com/s/l4oEJdskbWpff1E3tTNUxQ