There is some Leaking in Memory Leaking.

When programming in a language with a garbage collector (GC), memory leaks are usually not a concern because the language runtime periodically collects unused memory. However, we do need to be aware of some special scenarios that could lead to minor or real memory leaks. The rest of this article lists several such cases.

Minor memory leak caused by Substrings

The Go specification does not specify whether the result string and the base string involved in a substring expression should share the same underlying memory block that hosts the underlying byte sequence of both strings. The Go standard compiler/runtime does make them share the same underlying block of memory. It’s a good design, and it’s smart for both memory and CPU consumption. But it can cause a memory leak.

For example, after calling the following function f, there will be a memory leak of 1M bytes (slight) until the package-level variable s0 is modified elsewhere.

var s0 string // package level variable



func f(s1 string) {

// Suppose s1 is a string of length greater than 50.

s0 = s1[:50]

S0 and S1 now share the same underlying memory block.

// S1 is now dead, but s0 is still alive.

// Although there are only 50 bytes in the memory block,

// The fact that s0 is still alive prevents the 1M byte block from being reclaimed.

}

To avoid this slight memory leak, we can convert the substring to a []byte value, and then convert the []byte value back to string.

func f(s1 string) {

s0 = string([]byte(s1[:50]))

}

The disadvantage of the above approach to avoiding this minor memory leak is that 50 bytes of copy occur during the conversion, one of which is unnecessary.

We can use an optimization made by the Go standard compiler to avoid a copy, with the small extra cost of wasting a byte.

func f(s1 string) {

s0 = (" " + s1[:50])[1:]

}

The disadvantage of the above approach is that compiler optimizations may fail later, and the optimizations may not apply to other compilers.

A third way to avoid type memory leaks is to use strings.Builder, which wasn’t supported until Go 1.10.

import "strings"



func f(s1 string) {

var b strings.Builder

b.Grow(50)

b.WriteString(s1[:50])

s0 = b.String()

// b.reset () // if b is used elsewhere, it must be reset here.

}

The third approach has the disadvantage of being a bit verbose (by comparing the first two approaches).

Minor memory leaks caused by Subslices

Similar to finding substrings, finding subslices can cause minor memory leaks. In the following code, after the g function is called, most of the memory occupied by the block hosting the S1 element is lost (if there are no more values referencing the block).

var s0 []int



func g(s1 []int) {

// Suppose s1 has a length much greater than 30.

s0 = s1[len(s1)-30:]

}

If we want to avoid this slight memory leak, we must copy the 30 elements of S0 so that the survival of S0 does not prevent the memory block of s1 elements from being recycled.

func g(s1 []int) {

s0 = append([]int(nil), s1[len(s1)-30:]...)

}

Minor memory leak due to non-survivable slice element not reset pointer

In the following code, after calling g, the block of memory allocated to the first element of slice S is lost. The memory block allocated for the last element is also lost if the last element is never used as an element of any slice later.

func g() []*int {

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

return s[1:3]

}

If the returned slice is still alive, it will prevent the underlying chunk of s’s element from being collected, thus preventing the two chunks allocated from the first to the last element of S from being collected, even though the two elements are no longer alive.

If we want to avoid this slight memory leak, we have to reset Pointers in non-viable elements (here, the first and last elements are treated as non-viable after function h is called).

func h() []*int {

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

s1 := s[1:3]

s[0] = nil; s[len(s)-1] = nil

return s1

}

We often need to reset the pointer to a non-viable element in a slice element deletion operation.

Memory leak caused by lost Goroutines

Sometimes, for some logical error in code design, one or more goroutines will block forever, which will result in many code blocks used in these Goroutines never being garbage collected. This is a real memory leak.

For example, if you start a Goroutine with the following function and pass it a nil channel argument, the Goroutine will block forever. The Go runtime assumes that the Goroutine is still alive, so the memory block allocated for S will never be collected.

func k(c <-chan bool) {

s := make([]int64, 1e6)

If <-c {// If c is nil, this will always block

_ = s

// 使用 s, ...

}

}

We should avoid this logical error.

Finalizers (Finalizers)

Setting finalizer for members of a circular reference group may prevent all memory blocks allocated for this circular reference group from being collected. This is not a minor memory leak but a real one.

After the following functions are called and exit, the memory blocks allocated for X and Y are not guaranteed to be collected by the garbage collector in the future.

func memoryLeaking() {

type T struct {

v [1<<20]int

t *T

}



var finalizer = func(t *T) {

fmt.Println("finalizer called")

}



var x, y T



// SetFinalizer will cause X to escape to the heap.

runtime.SetFinalizer(&x, finalizer)



// The following statement will cause x and y to become uncollectable.

X.t, y.t = &y, &x // y also escaped to the heap.

}

So, avoid finalizers for values in circular reference groups.