Golang 作为一个提供了GC的语言,还能有内存泄漏一说?其实不然,Go 服务宕机80%应该是因为内存泄漏的缘故了。

导言

内存泄漏 (Memory Leak) 是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。(维基百科)
所以,内存泄漏是一个共性的问题。虽然在Golang中提供了聪明的GC操作,但是如果操作不慎,也可能掉入内存泄漏的坑。

什么情况下会内存泄漏

总结了一些经常碰到的内存泄漏的例子,以飨读者:
数据泄漏
比如,在全局变量(或者单例模式)中 (例如:map,slice 等 构成的数据池),不断添加新的数据,而不释放。
goroutine泄漏
goroutine 泄漏,应该是Golang 中经常遇到的一个问题了。由于goroutine 存在栈空间(至少会有2K), 所以goroutine 的泄漏常常导致了golang的内存泄漏。
在官方提供的方法中,如果使用不当,很容易出现goroutine泄漏。比如说:在 Time 包中:
1
2
3
4
5
6
7
8
9
10
func After(d Duration) <-chan Time {
return NewTimer(d).C
}
func Tick(d Duration) <-chan Time {
if d <= 0 {
return nil
}
return NewTicker(d).C
}
再比如说,在 Http 请求时,会返回 *http.Response 对象,Http 响应中的Body是http的响应数据,Body 需要每次读取后关闭。那为什么需要关闭呢,我们从 Body 的赋值代码查找结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// /src/net/http/transport.go
// http 的持久化链接池,不断取需要做的请求,并做响应
func (pc *persistConn) readResponse(rc requestAndChan, trace *httptrace.ClientTrace) (resp *Response, err error) {
//...
resp.Body = newReadWriteCloserBody(pc.br, pc.conn) // pc.conn net.Conn
// ...
}
func newReadWriteCloserBody(br *bufio.Reader, rwc io.ReadWriteCloser) io.ReadWriteCloser {
body := &readWriteCloserBody{ReadWriteCloser: rwc}
if br.Buffered() != 0 {
body.br = br
}
return body
}
除了官方的一些func使用不当会导致goroutine泄漏,日常开发也会碰到各种内存泄漏的例子, 比如说:redis 从连接池取的链接没有做释放,DB 的 stmt 没有关闭等。
总体来说,golang 内存泄漏仅此而已,全局变量导致的内存不断增大,或者goroutine泄漏。但是在使用时,goroutine 泄漏占到了绝大多数。所以,有内存泄漏时,多看看是不是哪里又忘了关链接了。
end
下篇文章我们将具体分析怎么从我们的服务中找出内存泄漏的点,以及如何监控和避免内存泄漏。