MemStats是一个结构体,里面指标很多,常用的有:
- HeapObjects:堆中已经分配的对象总数,GC内存回收后HeapObjects取值相应减小。
- HeapAlloc: 堆中已经分配给对象的字节数,GC内存回收后HeapAlloc取值相应减小。
- TotalAlloc: 堆中已经分配给对象的总的累计字节数,只增不减,GC内存回收后也不减小。
- HeapSys: 从操作系统为堆申请到的字节数。
- HeapIdle: 堆的闲置区间,包括已经归还给操作系统的物理字节数(HeapReleased)
- HeapReleased: 已经归还给操作系统的物理字节数,是HeapIdle的子集。
通常来说,一个进程所占用的常驻内存集RSS略大于 HeapIdle - HeapReleased (特殊情况是只声明了变量并未赋值,详见后文)。这些指标都是虚拟地址空间(virtual address space)并不完全对应于物料内存,Go13.8好像还没有直接获取RSS的函数。
测试代码:
为了方便,命名一个变量HeapRetained = HeapIdle - HeapReleased。
在函数f()中声明并赋值一个200MB的数组,记录函数调用前后各项内存指标的变化。RSS通过ps命令获取:
while :
do
ps aux | grep memcheck | grep -v " memcheck" | awk '{print systime()"\t"$6/1024}'
sleep 1
done
memcheck.go
package main
import (
"fmt"
_ "github.com/dustin/go-humanize"
"log"
"os"
"os/signal"
"runtime"
_ "strconv"
"syscall"
"time"
)
func toMegaBytes(bytes uint64) float64 {
return float64(bytes) / 1024 / 1024
}
func traceMemStats() {
var ms runtime.MemStats
runtime.ReadMemStats(&ms)
var result = make([]float64, 7)
result[0] = float64(ms.HeapObjects)
result[1] = toMegaBytes(ms.HeapAlloc)
result[2] = toMegaBytes(ms.TotalAlloc)
result[3] = toMegaBytes(ms.HeapSys)
result[4] = toMegaBytes(ms.HeapIdle)
result[5] = toMegaBytes(ms.HeapReleased)
result[6] = toMegaBytes(ms.HeapIdle - ms.HeapReleased)
fmt.Printf("%d\t", time.Now().Unix())
for _, v := range result {
fmt.Printf("%.2f\t", v)
}
fmt.Printf("\n")
time.Sleep(1 * time.Second)
}
func f() {
traceMemStats()
var container [200 * 1024 * 1024]byte
for i := 0; i < 200*1024*1024; i++ {
container[i] = 0
}
traceMemStats()
container[0] = 0
log.Printf("%d", len(container))
}
func main() {
time.Sleep(1 * time.Second)
log.Println("call f() from main")
f()
log.Println("back to main")
runtime.GC() // 调用强制gc函数
go func() {
for {
traceMemStats()
}
}()
log.Println("done.")
//信号阻塞避免进程退出
idleConnsClosed := make(chan struct{})
go func() {
sigint := make(chan os.Signal, 1)
signal.Notify(sigint, os.Interrupt, syscall.SIGINT, syscall.SIGTERM)
data := <-sigint
log.Printf("received signal: " + data.String())
log.Printf("start to shutdown...")
close(idleConnsClosed)
}()
<-idleConnsClosed
}
前面几个点的数据:
RSS | HeapObjects | HeapAlloc | TotalAlloc | HeapSys | HeapIdle | HeapReleased | HeapRetained | |
1592806152 | 5.05469 | 303 | 0.11 | 0.11 | 63.75 | 63.3 | 63.27 | 0.03 |
1592806153 | 5.15625 | 311 | 200.11 | 200.11 | 319.62 | 119.15 | 119.12 | 0.03 |
1592806154 | 213.508 | 253 | 0.09 | 200.12 | 319.66 | 319.23 | 119.16 | 200.07 |
1592806155 | 213.152 | 272 | 0.1 | 200.12 | 319.53 | 319.09 | 122.97 | 196.12 |
1592806156 | 209.168 | 279 | 0.1 | 200.12 | 319.53 | 319.09 | 126.88 | 192.22 |
1592806157 | 205.184 | 286 | 0.1 | 200.12 | 319.53 | 319.09 | 130.78 | 188.31 |
可以看到HeapRetained和RSS的曲线基本吻合。HeapSys约等于HeapIdle + HeapAlloc。
需要说明的是HeapAlloc ,TotalAlloc ,HeapSys,HeapIdle,HeapReleased 这些指标严格的定义是操作系统虚拟地址空间(virtual address space)并不一定对应于物理内存。比如说把上面的测试代码f()中赋值部分去掉:
func f() {
traceMemStats()
var container [200 * 1024 * 1024]byte
//for i := 0; i < 200*1024*1024; i++ {
// container[i] = 0
//}
traceMemStats()
container[0] = 0
log.Printf("%d", len(container))
}
可以看到,由ReadMemStats得到的各项指标和之前几乎完全一样而RSS却一直很小。这也证明了虚拟地址空间并不完全对应于物理内存。