内存泄漏场景

再推一次老貘的《Go101》。

 

子字符串造成的内存泄露

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)
}

 

其实我们可以先将s1这个内存块转移一下,这样gc就会自动标记清理。

func f(s1 string) {
    s0 = string([]byte(s1[:50]))
}

这个方法的缺点就是多了两次“复制”

 

func f(s1 string) {
    s0 = (" " + s1[:50])[1:]
}

这个就是利用编译器的优化了,具体可以看下ssa,涉及编译器优化,就不要作为首选了。

 

import "strings"

func f(s1 string) {
    var b strings.Builder
    b.Grow(50)
    b.WriteString(s1[:50])
    s0 = b.String()
}

这是一个看起来复杂,但却是最合理的思路。

 

上面讲了最合理的思路,但并不是最合理的方式,因为这种代码有坏的味道,现在golang提供了repeat支持,这是首选。

s0 = strings.Repeat(s1, 50)

可见实现思路还是一样的

func Repeat(s string, count int) string {
    if count == 0 {
        return ""
    }

    // Since we cannot return an error on overflow,
    // we should panic if the repeat will generate
    // an overflow.
    // See Issue golang.org/issue/16237
    if count < 0 {
        panic("strings: negative Repeat count")
    } else if len(s)*count/count != len(s) {
        panic("strings: Repeat count causes overflow")
    }

    n := len(s) * count
    var b Builder
    b.Grow(n)
    b.WriteString(s)
    for b.Len() < n {
        if b.Len() <= n/2 {
            b.WriteString(b.String())
        } else {
            b.WriteString(b.String()[:n-b.Len()])
            break
        }
    }
    return b.String()
}

 

子切片造成的内存泄露

需要了解切片的源码

var s0 []int

func g(s1 []int) {
    // 假设s1的长度远大于30。
    s0 = s1[len(s1)-30:]
}

 

这里我们可以用append来处理,这样就不会公用一个底层了

func g(s1 []int) {
    s0 = append(s1[:0:0], s1[len(s1)-30:]...)
}

 

因为未重置丢失的切片元素中的指针而造成的临时性内存泄露

func h() []*int {
    s := []*int{new(int), new(int), new(int), new(int)}
    // 使用此s切片 ...

    return s[1:3:3]
}

其实就是一个删除元素,但是由于是指针类型,我们需要置nil的

func h() []*int {
    s := []*int{new(int), new(int), new(int), new(int)}

    s[0], s[len(s)-1] = nil, nil // 重置首尾元素指针
    return s[1:3:3]
}

 

因为协程被永久阻塞而造成的永久性内存泄露

这个Dave Cheney说过,尽量少用协程,信道,为什么?你真的需要吗?如果你写的不能百分百掌控,这些都是埋雷,Golang新手偏爱协程,但是协程我个人认为你需要把调度和运行,channel和goroutine源码看懂再写,否则是有很大风险的。

 

因为没有停止不再使用的time.Ticker值而造成的永久性内存泄露

当一个time.Timer值不再被使用,一段时间后它将被自动垃圾回收掉。 但对于一个不再使用的time.Ticker值,我们必须调用它的Stop方法结束它,否则它将永远不会得到回收。

 

因为不正确地使用终结器(finalizer)而造成的永久性内存泄露

我坚决反对新手使用这个。

 

延迟调用函数导致的临时性内存泄露

for里使用defer

 

我认为sync.pool的源码是要学习的,否则你只是在背代码。

 

https://github.com/golang/go/pull/32138

https://cbsheng.github.io/posts/fasthttp%E6%BA%90%E7%A0%81%E6%9C%80%E4%BD%B3%E5%AE%9E%E8%B7%B5%E5%88%86%E6%9E%90/

https://www.zhihu.com/question/327580797

https://xargin.com/logic-of-slice-memory-leak/

https://github.com/golang/go/issues/20138

https://qcrao.com/2020/05/06/dive-into-go-sync-map/

 
posted @ 2020-05-08 10:04  zhangyu63  阅读(644)  评论(0编辑  收藏  举报