常见导致内存泄漏场景及防范方法
- 未关闭的文件描述符
- 场景:在Go语言中,如果打开了文件、网络连接等资源,但没有及时关闭,会导致文件描述符泄漏,随着不断打开新的资源,最终耗尽系统资源。例如使用
os.Open
打开文件后忘记调用file.Close()
。
- 防范:使用
defer
关键字在函数结束时关闭文件。如下示例:
func readFile() {
file, err := os.Open("test.txt")
if err != nil {
log.Fatal(err)
}
defer file.Close()
// 后续文件操作
}
- 未释放的goroutine
- 场景:当一个goroutine持有对某些对象的引用,而这些对象无法被垃圾回收器回收,且goroutine永不结束时,就会发生内存泄漏。例如,在一个goroutine中创建了一个大的切片,但是该goroutine一直处于运行状态,没有释放切片的内存。
- 防范:确保goroutine有合适的退出机制。可以通过通道(channel)发送信号来通知goroutine退出。示例如下:
func worker(done chan struct{}) {
bigSlice := make([]int, 1000000)
// 模拟工作
time.Sleep(time.Second)
<-done
}
func main() {
done := make(chan struct{})
go worker(done)
// 主程序工作一段时间后
time.Sleep(2 * time.Second)
close(done)
}
- 缓存使用不当
- 场景:如果在程序中使用了缓存(如map作为缓存),并且缓存中的数据不断增加但从不清理,会导致内存占用持续上升。例如,一个缓存函数结果的map,随着调用次数增加,缓存中数据无限增长。
- 防范:为缓存设置过期策略,定期清理缓存中的过期数据。可以使用定时任务结合一个带有过期时间的结构体来实现。示例如下:
type CachedValue struct {
value interface{}
expiration time.Time
}
var cache = make(map[string]CachedValue)
func setCache(key string, value interface{}, duration time.Duration) {
cache[key] = CachedValue{
value: value,
expiration: time.Now().Add(duration),
}
}
func getCache(key string) (interface{}, bool) {
if val, ok := cache[key]; ok {
if time.Now().After(val.expiration) {
delete(cache, key)
return nil, false
}
return val.value, true
}
return nil, false
}
func cleanCache() {
for key, val := range cache {
if time.Now().After(val.expiration) {
delete(cache, key)
}
}
}
func main() {
go func() {
for {
time.Sleep(5 * time.Minute)
cleanCache()
}
}()
// 其他业务逻辑
}