go语言内存“泄漏”

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的少了很多!

Comment

您的电子邮箱地址不会被公开。 必填项已用 * 标注