Golang堆Profile与runtime.Memstats结果差异及可信度疑问
Go内存统计差异:HeapInuse、pprof与VmRSS
现象描述
我拿到的runtime.Memstats数据显示HeapInuse约为14.76GB:
# Alloc =15850481912 # TotalAlloc = 14112784178272 # SyS =27469378624 # Lookups =0 # Mallocs = 220246619671 # Frees = 220208758959 # HeapAlloc = 15850481912 # HeapSys = 26495549440 # HeapIdle = 10622779392 # HeapInuse = 15872770048 # HeapReleased = 586293248 # HeapObjects = 37860712 # Stack = 4063232 / 4063232 # MSpan = 42770560 /213057600 # Mcache = 9600 /15600 # BuckHashsys = 2771335 # GCsys =732745144 # Othersys = 21176273 # LastGc = 1695642639886128527
但用go tool pprof查看sample_index=inuse_space的堆Profile时,总在用空间仅约12616.97MB,和HeapInuse存在明显差异:
Showing nodes accounting for 12551.20MB, 99.48% of 12616.97MB total Dropped 143 nodes (cum <= 63.08MB) Showing top 10 nodes out of 14 flat flat% sum% cum cum% 12288MB 97.39% 97.39% 12288MB 97.39% github.com/coocood/freecache.NewRingBuf (inline) 262.53MB 2.08% 99.47% 262.53MB 2.08% github.com/coocood/freecache.(*segment).expand (inline) 0.67MB 0.0053% 99.48% 12288.67MB 97.40% github.com/coocood/freecache.NewCacheCustomTimer
另外查看/proc/pid/stat的内存统计,VmRSS数值(约16.1GB)更接近HeapInuse:
VmPeak: 28930944 kB VmSize: 28865408 kB VmLck: 0 kB VmPin: 0 kB VmHWM : 16605472 kB VmRSS : 16605472 kB RssAnon: 16576956 kB RssFile: 28504 kB RssShmem: 12 kB VmData: 27090500 kB VmStk: 132 kB VmExe: 28488 kB VmLib: 4220 kB VMPTE: 34552 kB VmSwap: 0 kB
差异原因分析
1. Memstats.HeapInuse 与 pprof inuse_space
- *HeapInuse*:统计的是Go堆管理器从操作系统申请的、至少包含一个存活对象的内存块(span)的总大小。它不仅包含用户程序实际使用的对象内存(对应
HeapAlloc),还包括每个span内部的空闲碎片(比如一个8KB的span里只放了1KB对象,整个8KB都算入HeapInuse)。 - *pprof inuse_space*:仅统计用户程序实际持有、正在使用的存活对象的内存大小,是
HeapAlloc的近似值。但它存在局限性:- 无法追踪堆外内存(比如通过
syscall.Mmap、cgo直接分配的内存); - 若某些内存使用了特殊的内存管理机制(如自定义内存池、
unsafe.Pointer绕过GC追踪),可能被遗漏; - 采样或快照机制可能过滤掉小对象或合并节点(但你这里dropped的节点仅占63MB,不是主要差异来源)。
你这里的差异大概率是因为freecache的部分内存未被pprof完全追踪,或是程序中存在其他未被pprof统计的存活对象。
- 无法追踪堆外内存(比如通过
2. Memstats.HeapInuse 与 /proc/pid/stat VmRSS
- *VmRSS*:是进程实际占用的物理内存(驻留集大小),包含了所有进程使用的内存:Go堆(HeapInuse)、栈内存、程序代码、共享库、文件映射内存、cgo内存等。因此它的数值会比HeapInuse大,因为覆盖了非堆内存区域。你这里的差值(约1.3GB)正好对应栈、程序代码、共享库等非堆内存的总和,属于正常现象。
该信任哪个统计结果?
- 若要了解Go堆管理器从OS获取的、正在使用的堆内存总量(含碎片),选Memstats.HeapInuse;
- 若要分析用户程序中存活对象的内存分布,定位内存占用热点,选pprof inuse_space(注意排查堆外内存是否被遗漏);
- 若要了解进程实际占用的物理内存总量,选
/proc/pid/stat的VmRSS。
内容的提问来源于stack exchange,提问作者Jacky Zhao
相关产品推荐
相关产品推荐

