Go的sync.map删除元素,内存会自动释放吗?

本文探讨了Go的sync.map与原生map在删除元素后内存是否自动释放的问题。结论指出,删除值类型元素时内存不会自动释放,而删除引用类型元素时,仅释放子元素的内存。通过实验展示了sync.map在执行delete操作后,其dirty部分的内存被回收,而read部分保持不变。实验进一步包含了嵌套sync.map的情况。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值