对于频繁增删map的场景,我们很关心map的内存是否会自动释放。
先说结论:-
如果删除的元素是值类型,如int,float,bool,string以及数组和struct,map的内存不会自动释放
-
如果删除的元素是引用类型,如指针,slice,map,chan等,map的内存会自动释放,但释放的内存是子元素应用类型的内存占用
-
将map设置为nil后,内存被回收
以子元素是整形和map为例
重要一点:申请一个全局map来保证内存被分配到堆上面
原因请参照:golang的gc回收针对堆还是栈?变量内存分配在堆还是栈?
实验1
普通的map,map保存到是int到int的映射,会执行delete删除map的每一项,执行垃圾回收,看内存是否被回收,map设置为nil,再看是否被回收。
package main
import (
"log"
"runtime"
)
var lastTotalFreed uint64
var intMap map[int]int
var cnt = 8192
func main() {
printMemStats()
initMap()
runtime.GC()
printMemStats()
log.Println(len(intMap))
for i := 0; i < cnt; i++ {
delete(intMap, i)
}
log.Println(len(intMap))
runtime.GC()
printMemStats()
intMap = nil
runtime.GC()
printMemStats()
}
func initMap() {
intMap = make(map[int]int, cnt)
for i := 0; i < cnt; i++ {
intMap[i] = i
}
}
func printMemStats() {
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)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)
lastTotalFreed = m.TotalAlloc - m.Alloc
}
看结果前,解释下几个字段:
- Alloc:当前堆上对象占用的内存大小。
- TotalAlloc:堆上总共分配出的内存大小。
- Sys:程序从操作系统总共申请的内存大小。
- NumGC:垃圾回收运行的次数。
结果如下:
2019/12/19 11:48:03 Alloc = 89 TotalAlloc = 89 Just Freed = 0 Sys = 1700 NumGC = 0
2019/12/19 11:48:03 Alloc = 403 TotalAlloc = 437 Just Freed = 33 Sys = 3234 NumGC = 1
2019/12/19 11:48:03 8192
2019/12/19 11:48:03 0
2019/12/19 11:48:03 Alloc = 404 TotalAlloc = 438 Just Freed = 1 Sys = 3234 NumGC = 2
2019/12/19 11:48:03 Alloc = 91 TotalAlloc = 439 Just Freed = 313 Sys = 3234 NumGC = 3
Alloc代表了map占用的内存大小,这个结果表明,执行完delete后,map占用的内存并没有变小,Alloc依然是403,代表map的key和value占用的空间仍在map里.执行完map设置为nil,Alloc变为91,与刚创建的map大小基本是约等于。
实验二map套子map,顶层map是int到子map的映射,子map是int到int的映射,同样先执行delete,再设置为nil,分别看垃圾回收情况。
package main
import (
"log"
"runtime"
)
var intMapMap map[int]map[int]int
var cnt = 1024
var lastTotalFreed uint64 // size of last memory has been freed
func main() {
// 1
printMemStats()
// 2
initMapMap()
runtime.GC()
printMemStats()
// 3
fillMapMap()
runtime.GC()
printMemStats()
// 4
log.Println(len(intMapMap))
for i := 0; i < cnt; i++ {
delete(intMapMap, i)
}
log.Println(len(intMapMap))
runtime.GC()
printMemStats()
// 5
intMapMap = nil
runtime.GC()
printMemStats()
}
func initMapMap() {
intMapMap = make(map[int]map[int]int, cnt)
for i := 0; i < cnt; i++ {
intMapMap[i] = make(map[int]int, cnt)
}
}
func fillMapMap() {
for i := 0; i < cnt; i++ {
for j := 0; j < cnt; j++ {
intMapMap[i][j] = j
}
}
}
func printMemStats() {
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)-lastTotalFreed)/1024, m.Sys/1024, m.NumGC)
lastTotalFreed = m.TotalAlloc - m.Alloc
}
2019/12/19 11:49:59 Alloc = 89 TotalAlloc = 89 Just Freed = 0 Sys = 1700 NumGC = 0
2019/12/19 11:50:00 Alloc = 41171 TotalAlloc = 41204 Just Freed = 32 Sys = 46026 NumGC = 5
2019/12/19 11:50:00 Alloc = 41259 TotalAlloc = 41342 Just Freed = 49 Sys = 46026 NumGC = 6
2019/12/19 11:50:00 1024
2019/12/19 11:50:00 0
2019/12/19 11:50:00 Alloc = 132 TotalAlloc = 41343 Just Freed = 41129 Sys = 46026 NumGC = 7
2019/12/19 11:50:00 Alloc = 91 TotalAlloc = 41344 Just Freed = 41 Sys = 46026 NumGC = 8
这个结果表明,在执行完delete后,顶层map占用的内存从41259降到了132,子层map占用的空间肯定是被GC回收了,不然占用内存不会下降这么显著。但依然比初始化的顶层map占用的内存89多出不少,那是因为delete操作,顶层map的key占用的空间依然在map里,当把顶层map设置为nil时,大小变为91吗,顶层map占用那些空间被释放了.
参考资料:
- https://blog.csdn.net/m0_43499523/article/details/85851478
- http://blog.cyeam.com/json/2017/11/02/go-map-delete