什么是内存泄漏
程序运行中,动态分配的内存未能正确释放或回收,导致内存一直被占用不能被正确释放,可能导致最终内存耗尽
- 系统资源耗尽:严重的内存泄漏可能会导致系统内存耗尽,进而引发系统崩溃或其他程序无法正常运行。
- 程序崩溃:如果程序无法分配到足够的内存,它可能会崩溃,尤其是在没有进行异常处理的情况下。
常见场景
协程被永久阻塞
func leakyGoroutine() {
ch := make(chan int)
go func() {
<-ch // Goroutine 永远在等待接收数据,但没有地方向该通道发送数据
}()
}
context未及时取消
func leakyContext() {
ctx, _ := context.WithCancel(context.Background())
go func() {
<-ctx.Done() // 如果取消函数未被调用,Goroutine 将一直等待
}()
}
不正确的资源管理
func leakyFile() {
file, err := os.Open("example.txt")
if err != nil {
return
}
// 忘记关闭文件,导致文件描述符泄漏
}
没有停止不再使用的time.Ticker
func leakyTicker() {
counter := 0
ticker := time.NewTicker(time.Second)
//defer ticker.Stop()
for range ticker.C {
if counter == 3 {
return
}
fmt.Println("-----")
counter++
}
}
子切片造成的暂时性内存泄漏
var s0 []int
func g(s1 []int){
s0=s1[len(s1)-30:] //s0引用着s1的内存块,s1的内存块得不到释放
}
可以使用copy,使得s0和s1不共用内存
var s0 []int
func g (s1 []int){
copy(s0,s1[len(s1)-30:])
}
子字符串(复制操作)造成的暂时性内存泄漏
var s0 string // 一个包级变量
// 一个演示目的函数。
func f(s1 string) {
s0 = s1[:50]
// 目前,s0和s1共享着承载它们的字节序列的同一个内存块。
// 虽然s1到这里已经不再被使用了,但是s0仍然在使用中,
// 所以它们共享的内存块将不会被回收。虽然此内存块中
// 只有50字节被真正使用,而其它字节却无法再被使用。
}
func demo() {
s := createStringWithLengthOnHeap(1 << 20) // 1M bytes
f(s)
}
func createStringWithLengthOnHeap(length int) string {
bytes := make([]byte, length)
return string(bytes)
}
可以使用一下方式防止内存泄漏
- 使用strings.Builder
- 使用stings.Clone()函数
未重置丢失的切片元素中的指针
func h() []*int {
s := []*int{new(int), new(int), new(int), new(int)}
//s[0] = nil
return s[1:] //导致内存泄漏
}
循环引用
type Node struct {
Value int
Children []*Node
Parent *Node
}
func leakyReference() {
parent := &Node{}
child := &Node{Parent: parent}
parent.Children = append(parent.Children, child) // 父节点引用子节点,子节点引用父节点
}
总结
尽管 Go 的垃圾回收机制可以自动管理内存,但仍需要注意编写代码时避免上述场景,以防止内存泄漏影响程序性能和稳定性。