go语言虽然有gc 的机制,但是在go1.12 之后会默认开启MADV_FREE ,这个功能的作用是内存懒回收
(但是存在一个问题就是linx的内存懒回收:比如操作系统有1g的内存,一个应用申请了950M,但是其实真实使用的物理内存可能只有100M,剩下850M内存已经通过GC的方式通知操作系统可以回收了,但是操作系统为了性能没有回收,认为程序可能后面还要使用到,这样不用下次再分配,当有第二个进程要申请60M 内存的时候,由于操作系统的剩余内存不够,这时候才会将那部分内存回收)
但是针对 MADV_FREE 这个机制对Cgroup是否生效,我产生了疑问,如果我k8s 容器限制了600M,当我进程内存一直上涨是否会由于是Cgroup的限制导致进程无用内存没有被回收导致被oom?
因此我做了个实验:
1、写了一个程序进行测试;该程序是开启10个goroutine,每个goroutine 存活的时间以20秒为阶梯上升。直到200秒时间到,所有goroutine退出.通过 memStatus.HeapAlloc
查看堆内存分配
package main
import (
"flag"
"fmt"
"net/http"
_ "net/http/pprof"
"runtime"
"sync"
"time"
)
var count = flag.Int("count", 6000000, "NSQ topic")
func main() {
flag.Parse()
go func() {
http.ListenAndServe("localhost:9999", nil)
}()
go func() {
for {
memStatus := runtime.MemStats{}
runtime.ReadMemStats(&memStatus)
//打印堆内存
fmt.Printf("申请的内存:%d\\n", memStatus.HeapAlloc)
fmt.Printf("释放的内存次数:%d\\n", memStatus.Frees)
time.Sleep(2 * time.Second)
}
}()
workers := 10
var workerDone sync.WaitGroup
workerDone.Add(workers)
for i := 0; i < workers; i++ {
go func(i int) {
fmt.Println(i)
arr := make([]int, *count)
for i, _ := range arr {
arr[i] = i*i + 2
}
for i, _ := range arr {
arr[i] = arr[i]*i + 2
}
time.Sleep(time.Second * time.Duration(i) * 20)
workerDone.Done()
}(i)
}
workerDone.Wait()
fmt.Println("=====================================> end")
time.Sleep(10 * time.Minute)
}
2、将容器的内存限制在600M
3、在容器中启动进程,观察内存使用情况:
观察到进程在执行到一定时间后,内粗由480M 缩减到了194kb
同时top 观察内存发现 RSZ的内存还是一直在最大值
说明确实没有还给操作系统,这个时候可以使用命令
cat /proc/$pid/smaps | grep LazyFree
这个时候我们不关闭这个testMem 进程,在启动一个新的testMem ,观察进程是被kill 了还是,将第一个testMem 的内存回收了
可以看到内存瞬间会受到了只有55M 说明对于MADV_FREE cgroup是生效的.
再执行 cat /proc/299/smaps | grep LazyFree
LazyFree的少了很多!