go语言有完备的垃圾回收(gc)机制,但仍然可能出现内存泄漏的情况,如下:
开启一个协程:
func test() {
......
go func(){
for {
// do sth..
}
}()
.....
}
test()协程泄漏
TtlMap
strcut TtlMap {
...
}
func New() *TtlMap {
map := &TtlMap{
...
}
//启动定期清理协程
go map.clear()
return map
}
func (m *TtlMap) clear() {
for {
//定期清理...
}
}
map.clear()
strcut TtlMap {
...
stop chan bool
}
func New() *TtlMap {
map := &TtlMap{
...
}
//启动定期清理协程
go map.clear()
return map
}
func (m *TtlMap) clear() {
for {
select {
// 关闭
case <-m.stop:
return
//定期清理...
}
}
}
func (m *TtlMap) Close() {
m.stop <- true
}
这么做不太安全,当使用者忘记调用Close()方法关闭map,仍然会导致泄漏。
如何不调用Close,又能让Gc回收呢?可以在初始化时,把ttlmap包一层再返回:
strcut TtlMap {
...
stop chan bool
}
// 包裹定义
struct MapWarpper {
*TtlMap
}
func New() *MapWarpper {
map := &TtlMap{
...
}
go map.clear()
// 包一层
mw := &MapWarpper{map}
// 重点在此:设置被回收时操作
runtime.SetFinalizer(mw, onGarbageCollect)
return mw
}
func (m *TtlMap) clear() {
for {
select {
// 关闭
case <-m.stop:
return
//定期清理...
}
}
}
func onGarbageCollect(m *TtlMap) {
m.stop <- true
}
SetFinalizer
如果需要在一个对象object被从内存中移除前执行一些特殊操作,比如写日志等,可以通过调用以下方式调用函数来实现
runtime.SetFinalizer(obj, func(obj *typeObj))
在对象被GC进程选中并从内存中移除前,SetFinalizer都不会执行,即使程序正常结束或者发生错误
MapWarpper作为局部变量时,定义它的函数结束后,MapWarpper的生命周期已结束,Gc会将其回收。
Gc回收MapWarpper时执行了onGarbageCollect()函数,将Ttlmap的clear协程关闭,进而将Ttlmap回收。
这样,借助go的Gc机制,我们实现了一个安全的map库,无需担心内存泄漏发生。
-
问题1:不能直接给ttlmap设置SetFinalizer吗?
答:不能。只要ttlmap.clear()协程一直在运行,Gc就无法选中ttlmap对象执行垃圾回收。
实际上,给ttlmap设置了SetFinalizer后,如果ttlmap.clear()协程没有return,而是阻塞了,Gc就可以将ttlmap回收掉。 -
问题2:使用了Warpper,却没有给Warpper设置SetFinalizer,Warpper还会被回收吗?
答:经测试,不会被回收。
您可能感兴趣的文章:
golang编程技巧:利用GC机制优雅地关闭协程,避免内存泄漏
Golang垃圾回收机制
golang垃圾回收
golang runtime 简析
Golang GC原理
Go GC垃圾回收机制
Go 语言的核心优势
go语言有哪些优势?Go语言的核心特性有哪些
使用pprof进行golang程序内存分析
定位分析内存泄漏的原因和后果