Go 和 Python 都有垃圾回收机制。通常情况下,我们创建对象后,等对象没有任何引用时,运行时会自动回收它们。
例如 Go:
func createUser() {
user := &User{Name: "Alice"}
fmt.Println(user.Name)
}createUser 执行结束后,user 不再被引用,后续可以被 GC 回收。
但如果我们把它放进一个全局变量、缓存、长期运行的 goroutine,或者没有退出的队列中,它就依然“可达”。GC 不会判断它是不是“业务上没用了”,只能判断“是否仍被引用”。
所以,垃圾回收解决的是“手动释放内存”的问题,并不能替我们判断对象是否还应该存在。
常见的内存泄漏情况
1. 全局缓存只进不出
最直观的例子是缓存。
var cache = make(map[string][]byte)
func save(key string, data []byte) {
cache[key] = data
}如果服务持续运行、key 持续增加,cache 会越来越大。即使某些数据早就没用了,因为它们还被 map 引用,GC 也不会回收。
解决思路通常是:
设置缓存过期时间;
限制缓存最大容量;
使用带淘汰策略的缓存,例如 LRU;
明确哪些数据不应该放入全局缓存。
2. goroutine 没有退出
Go 的 goroutine 很轻量,但不是没有成本。如果不断创建 goroutine,又没有让它们退出,内存和调度资源都会持续被占用。
例如:
func worker(ch <-chan int) {
for {
value := <-ch
fmt.Println(value)
}
}如果 ch 后续不再发送数据,但也没有关闭,worker 会永远阻塞在这里,goroutine 无法退出。
更常见的写法是配合 context.Context:
func worker(ctx context.Context, ch <-chan int) {
for {
select {
case value, ok := <-ch:
if !ok {
return
}
fmt.Println(value)
case <-ctx.Done():
return
}
}
}以前我会把这种问题只理解为“协程泄漏”,但它本质上也会带来内存问题:goroutine 的栈、局部变量,以及它引用的对象都可能一直无法释放。
3. 切片引用了一个很大的底层数组
这是 Go 里很容易忽略的一类问题。
data := make([]byte, 10*1024*1024) // 10 MB
small := data[:10]虽然 small 只有 10 个字节,但它仍然引用着原来的 10 MB 底层数组。如果 small 被长期保存,大数组就不能被回收。
如果只需要小切片的数据,可以复制一份:
smallCopy := make([]byte, 10)
copy(smallCopy, data[:10])或者:
smallCopy := append([]byte(nil), data[:10]...)这个问题提醒我:看内存占用时,不能只看切片的 len,还要考虑它背后到底引用了多少数据。
4. 定时器和资源没有停止或关闭
比如 time.Ticker、文件、HTTP 响应体、数据库查询结果等,都应当在合适的时候释放。
ticker := time.NewTicker(time.Second)
defer ticker.Stop()HTTP 请求也要注意关闭响应体:
resp, err := http.Get(url)
if err != nil {
return err
}
defer resp.Body.Close()严格说,这些有些属于资源泄漏,而不只是堆内存泄漏。但长期运行的服务里,它们往往会一起出现:文件描述符耗尽、连接池堆积、内存持续上升,最后让服务变得不稳定。
内存泄漏在python中情况也是类似,比如全局列表或者字典不断累积,文件或者网络连接没有被正确关闭(一般使用with管理资源)
with open("data.txt", "r", encoding="utf-8") as file:
content = file.read()这样即使中途出现异常,文件也会被正确关闭。
对于数据库连接、HTTP 客户端会话、异步任务,同样需要有明确的关闭逻辑。
总结
我现在对内存泄漏的理解是:
内存泄漏不只是忘记释放内存,更常见的是忘记让不再需要的对象“失去引用”。
Go 和 Python 的 GC 能帮我们回收无用对象,但它们不会知道业务上哪些数据已经过期,也不会主动结束 goroutine、清理无限增长的缓存。
因此,写长期运行的程序时,比起只关心“能不能跑”,还应该多问一句:
这个对象、缓存、协程或连接,什么时候应该结束?