sync.map是什么?
请参照博客:Go1.9 sync.Map揭秘
go的原生map删除元素,内存会自动释放吗?
请参照博客:Go的原生map中删除元素,内存会自动释放吗?
先说结论:
-
如果删除的元素是值类型,如int,float,bool,string以及数组和struct,map的内存不会自动释放
-
如果删除的元素是引用类型,如指针,slice,map,chan等,map的内存会自动释放,但释放的内存是子元素应用类型的内存占用
map和sync.map两者区别:
乍一看除了sync.map不能为nil之外,两者没什么区别。但其实不然
具体可见如下的实验
小知识:sync.map开箱即用,不能赋值为nil
初始值为:sync.Map{mu:sync.Mutex{state:0, sema:0x0}, read:atomic.Value{v:interface {}(nil)}, dirty:map[interface {}]*sync.entry(nil), misses:0}
重要一点:
申请一个全局map来保证内存被分配到堆上面
实验1
普通的sync.map,保存的是int到int的映射,会执行delete删除每一项,执行垃圾回收,看内存是否被回收,
package main
import (
"fmt"
"log"
"runtime"
"sync"
)
var lastTotalFreed uint64
var intMap sync.Map
var cnt = 80920
func main() {
printMemStats()
initMap()
runtime.GC()
printMemStats()
fmt.Printf("%#v", intMap)
fmt.Println()
for i := 0; i < cnt; i++ {
intMap.Delete(i)
}
fmt.Printf("%#v", intMap)
fmt.Println()
runtime.GC()
printMemStats()
}
func initMap() {
for i := 0; i < cnt; i++ {
intMap.Store(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:垃圾回收运行的次数。
- 中间会以go的语法形式打印出来
结果如下:
2019/12/19 15:00:45 Alloc = 89 TotalAlloc = 89 Just Freed = 0 Sys = 1700 NumGC = 0
2019/12/19 15:00:46 Alloc = 6794 TotalAlloc = 12123 Just Freed = 5328 Sys = 13522 NumGC = 3
打印结果(太多暂省略)
打印结果(太多暂省略)
2019/12/19 15:00:46 Alloc = 3634 TotalAlloc = 29595 Just Freed = 20633 Sys = 29978 NumGC = 6
Alloc代表了map占用的内存大小,这个结果表明,执行完delete后,map占用的内存明显表变小,从6794变到了3634。***!!!!看到这里你可能就会疑惑了***,不是前面说不会进行垃圾回收的吗?怎么就内存占用变小了?
下面我们把cnt(元素数目)改小,具体看一下打印结果
2019/12/19 15:38:05 Alloc = 90 TotalAlloc = 90 Just Freed = 0 Sys = 1700 NumGC = 0
2019/12/19 15:38:06 Alloc = 92 TotalAlloc = 125 Just Freed = 33 Sys = 2084 NumGC = 1
2019/12/19 15:38:06 Alloc = 92 TotalAlloc = 128 Just Freed = 2 Sys = 2340 NumGC = 2
sync.Map{mu:sync.Mutex{state:0, sema:0x0}, read:atomic.Value{v:sync.readOnly{m:map[interface {}]*sync.entry(nil), amended:true}}, dirty:map[interface {}]*sync.entry{0:(*sync.entry)(0xc042074020), 2:(*sync.entry)(0xc042074030), 6:(*sync.entry)(0xc042074050), 7:(*sync.entry)(0xc042074058), 8:(*sync.entry)(0xc042074060), 9:(*sync.entry)(0xc042074068), 1:(*sync.entry)(0xc042074028), 3:(*sync.entry)(0xc042074038), 4:(*sync.entry)(0xc042074040), 5:(*sync.entry)(0xc042074048)}, misses:0}
sync.Map{mu:sync.Mutex{state:0, sema:0x0}, read:atomic.Value{v:sync.readOnly{m:map[interface {}]*sync.entry(nil), amended:true}}, dirty:map[interface {}]*sync.entry{}, misses:0}
我们暂不去关心整体打印顺序的变化,这是由于go自身启用立即回收的原因
只看两次sync.map的打印结果,可以明显看出sync.map的read部分没有改变,dirty部分在执行完delete操作之后被回收。是的!!!前面看到的内存占用数减少就是dirty这部分被清除的结果。
关于为何dirty数据会被清除请参照文章开头链接博客
实验二
sync.map套子sync.map,顶层sync.map是int到子map的映射,子sync.map是int到int的映射,同样执行delete,看垃圾回收情况。
给出代码,分析过程同实验一
package main
import (
"fmt"
"log"
"runtime"
"sync"
)
var intMapMap sync.Map
var cnt = 800
var lastTotalFreed uint64 // size of last memory has been freed
func main() {
// 1
printMemStats()
// 2
initMapMap()
runtime.GC()
printMemStats()
fmt.Printf("%#v", intMapMap)
fmt.Println()
for i := 0; i < cnt; i++ {
intMapMap.Delete(i)
}
fmt.Printf("%#v", intMapMap)
fmt.Println()
runtime.GC()
printMemStats()
}
func initMapMap() {
for i := 0; i < cnt; i++ {
intMapMap.Store(i, make(map[int]int, cnt))
}
}
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
}