go语言的map回收机制

在 Golang 中的 map 结构,在删除键值对的时候,并不会真正的删除,只是标记当前的key状态为empty。我们下面的程序作为例子,看看当我们在删除map中键值对时的内存变化,并了解如才能真正实现对键值对的垃圾回收。

initMap()
package main

import (
    "log"
    "runtime"
)

var lastFreed uint64

type element struct {
    X int
    Y int
}

var EleMap map[int]*element

const Num = 10000

func main() {
    printMemory()
    runtime.GC()

    initMap()
    runtime.GC()
    printMemory()

    delMap()
    runtime.GC()
    printMemory()

    EleMap = nil
    runtime.GC()
    printMemory()

}

func initMap() {
    EleMap = make(map[int]*element)
    for i := 0; i < Num; i++ {
        EleMap[i] = &element{i * 2, i * 3}
    }
}

func delMap() {
    for i := 0; i < Num; i++ {
        delete(EleMap, i)
    }
}

func printMemory() {
    var m runtime.MemStats
    runtime.ReadMemStats(&m)

    log.Printf("Alloc=%v||TotalAlloc=%v||Just_Freed=%v||Sys=%v||numGc=%v\n",
        m.Alloc/1024, m.TotalAlloc/1024, ((m.TotalAlloc-m.Alloc)-lastFreed)/1024, m.Sys/1024, m.NumGC)

    lastFreed = m.TotalAlloc - m.Alloc
}

程序运行结果如下:

2022/02/09 22:38:38 Alloc=159||TotalAlloc=159||Just_Freed=0||Sys=8019||numGc=0
2022/02/09 22:38:38 Alloc=628||TotalAlloc=991||Just_Freed=362||Sys=8658||numGc=2
2022/02/09 22:38:38 Alloc=474||TotalAlloc=993||Just_Freed=156||Sys=8658||numGc=3
2022/02/09 22:38:38 Alloc=162||TotalAlloc=994||Just_Freed=313||Sys=8914||numGc=4

通过第三行日志可以看出,当我们删除所有的键值对,并执行垃圾回收之后,当前分配的内存为474,而非初始状态的内存159。可以看出有相当一部分内存并没有被回收。因此删除键值对并不能保证背后的内存也被回收。

在某些系统中,map会作为缓存来存储数据,即使按照超时时间,定期删除某些键值对,也难以保证缓存占用的内存会被释放,会导致系统有内存泄漏的风险。