1.内存泄漏归纳
简单归纳一下,还是“临时性”内存泄露和“永久性”内存泄露:
临时性泄露,指的是该释放的内存资源没有及时释放,对应的内存资源仍然有机会在更晚些时候被释放,即便如此在内存资源紧张情况下,也会是个问题。这类主要是 string、slice 底层 buffer 的错误共享,导致无用数据对象无法及时释放,或者 defer 函数导致的资源没有及时释放。
永久性泄露,指的是在进程后续生命周期内,泄露的内存都没有机会回收,如 goroutine 内部预期之外的for-loop或者chan select-case导致的无法退出的情况,导致协程栈及引用内存永久泄露问题。
2.Go101总结 常见内存泄漏的情况
Go程序可能会在一些情况下造成内存泄漏。go101网站总结了各种内存泄漏的情况:
2.1.获取长字符串中的一段导致长字符串未释放
var s0 string // a package-level variable
// A demo purpose function.
func f(s1 string) {
s0 = s1[:50]
// Now, s0 shares the same underlying memory block
// with s1. Although s1 is not alive now, but s0
// is still alive, so the memory block they share
// couldn't be collected, though there are only 50
// bytes used in the block and all other bytes in
// the block become unavailable.
}
解决方案
func f(s1 string) {
s0 = (" " + s1[:50])[1:]
}
2.2. 获取长slice中的一段导致长slice未释放
var s0 []int
func g(s1 []int) {
// Assume the length of s1 is much larger than 30.
s0 = s1[len(s1)-30:]
}
func demo() {
s := createStringWithLengthOnHeap(1 << 20) // 1M bytes
f(s)
}
解决方案:
func g(s1 []int) {
s0 = make([]int, 30)
copy(s0, s1[len(s1)-30:])
// Now, the memory block hosting the elements
// of s1 can be collected if no other values
// are referencing the memory block.
}
2.3.长slice新建slice导致泄漏
func h() []*int {
s := []*int{new(int), new(int), new(int), new(int)}
// do something with s ...
return s[1:3:3]
}
解决方案:
func h() []*int {
s := []*int{new(int), new(int), new(int), new(int)}
// do something with s ...
// Reset pointer values.
s[0], s[len(s)-1] = nil, nil
return s[1:3:3]
}
2.4.goroutine泄漏
package main
import (
"fmt"
_ "net/http/pprof"
"time"
)
func main() {
ch := make(chan int)
for {
time.Sleep(1 * time.Second)
alloc(ch)
}
}
func alloc(ch chan<- int) {
go func() {
fmt.Println("new goroutine")
ch <- 0
fmt.Println("finished goroutine")
}()
}
输出
GOROOT=/usr/local/go #gosetup
GOPATH=/Users/qicycle/Go #gosetup
/usr/local/go/bin/go build -o /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_goroutineLeak_go -gcflags all=-N -l /System/Volumes/Data/Users/qicycle/Documents/我的测试/golang/memoak/goroutineLeak.go #gosetup
/Applications/GoLand.app/Contents/plugins/go/lib/dlv/mac/dlv --listen=0.0.0.0:51575 --headless=true --api-version=2 --check-go-version=false --only-same-user=false exec /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_goroutineLeak_go --
API server listening at: [::]:51575
debugserver-@(#)PROGRAM:LLDB PROJECT:lldb-1100.0.30..1
for x86_64.
Got a connection, launched process /private/var/folders/26/ynhz7g5n3xg19q_bnpqcjpdc0000gn/T/___go_build_goroutineLeak_go (pid = 3348).
new goroutine
new goroutine
new goroutine
new goroutine
new goroutine
new goroutine
new goroutine
new goroutine
^Znew goroutine
new goroutine
new goroutine
new goroutine
new goroutine
通过输出可以看到,只有new goroutine,没有finished goroutine。
goroutine泄露导致内存泄露: channel有写没有读的情况,导致goroutine一直阻塞着无法完成形成泄漏
goroutine无法完成的情况有很多种,比如互斥锁没有释放,互斥锁死锁等等
2.5. time.Ticker未关闭导致泄漏
import (
"fmt"
_ "net/http/pprof"
"time"
)
func main() {
timer := time.NewTicker(time.Duration(2) * time.Second)
//defer timer.Stop()
for true {
select {
case <-timer.C:
fmt.Println("on time")
default:
time.Sleep(1 * time.Second)
}
}
}
2.6. Finalizer导致泄漏
x,y内存逃逸到栈中了,
func memoryLeaking() {
type T struct {
v [1<<20]int
t *T
}
var finalizer = func(t *T) {
fmt.Println("finalizer called")
}
var x, y T
// The SetFinalizer call makes x escape to heap.
runtime.SetFinalizer(&x, finalizer)
// The following line forms a cyclic reference
// group with two members, x and y.
// This causes x and y are not collectable.
x.t, y.t = &y, &x // y also escapes to heap.
}
2.7. Deferring Function Call导致泄漏
大量的文件只有等待函数结束才释放,属于临时泄漏
func writeManyFiles(files []File) error {
for _, file := range files {
f, err := os.Open(file.path)
if err != nil {
return err
}
defer f.Close()
_, err = f.WriteString(file.content)
if err != nil {
return err
}
err = f.Sync()
if err != nil {
return err
}
}
return nil
}
解决方案:
打开一个释放一个
func writeManyFiles(files []File) error {
for _, file := range files {
if err := func() error {
f, err := os.Open(file.path)
if err != nil {
return err
}
// The close method will be called at
// the end of the current loop step.
defer f.Close()
_, err = f.WriteString(file.content)
if err != nil {
return err
}
return f.Sync()
}(); err != nil {
return err
}
}
return nil
}
3. 其他泄漏/不正当使用内存
3.1. 内存分配没有释放
以下实例内存一直申请,但是没有释放,造成内存泄漏
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
"os"
"time"
)
// 内存堆积没有释放
func main() {
// 开启pprof
go func() {
ip := "0.0.0.0:6060"
if err := http.ListenAndServe(ip, nil); err != nil {
fmt.Printf("start pprof failed on %s\n", ip)
os.Exit(1)
}
}()
tick := time.Tick(time.Second / 100)
var buf []byte
for range tick {
buf = append(buf, make([]byte, 1024*1024)...)
}
}
3.2. 大数组作为参数导致短期内内存激增
由于数组是Golang的基本数据类型,每个数组占用不同的内存空间,生命周期互不干扰,很难出现内存泄漏的情况。
但是数组作为形参传输时,遵循的是值拷贝,如果函数被多次调用且数组过大时,则会导致内存使用激增。
func countTarget(nums [1000000]int, target int) int{
num := 0
for i:=0; i<len(nums) && nums[i] == target; i++{
num ++
}
return num
}
例如上面的函数中,每次调用countTarget函数传参时都需要新建一个大小为100万的int数组,大约为8MB内存,
如果在短时间内调用100次就需要约800MB的内存空间了。(未达到GC时间或者GC阀值是不会触发GC的)
如果是在高并发场景下每个协程都同时调用该函数,内存占用量是非常恐怖的。
因此对于大数组放在形参场景下,通常使用切片或者指针进行传递,避免短时间的内存使用激增。
3.3. goroutine阻塞拥挤等待,内存浪费
10个生产者1秒生产一次,同时只有1个消费者1秒消费一次,导致9个生产者都在阻塞等待,浪费内存资源
package main
import (
"fmt"
"time"
)
var ch = make(chan string, 1)
func producer(){
for i:=0; i<10; i++ {
go func(pi int) {
n := 0
for {
n += 1
ch <- fmt.Sprintf("%d_%d", pi, n)
fmt.Printf("%s producer write p:%d, n:%d\n", time.Now().String(), pi, n)
time.Sleep(1 * time.Second)
}
}(i)
}
}
func consumer(){
for {
gotS := <- ch
fmt.Printf("%s consumer read n:%s\n", time.Now().String(), gotS)
time.Sleep(1 * time.Second)
}
}
func main() {
go consumer()
go producer()
select{
}
}
4. 需要手动管理内存吗?
go语言不需要手动管理内存;go语言内置内存管理功能(GC机制),开发者不需要关心内存的申请和释放,这样为使用者带来极大的便利。
什么是GC,又有什么用?
GC,全称 Garbage Collection,即垃圾回收,是一种自动内存管理的机制。
当程序向操作系统申请的内存不再需要时,垃圾回收主动将其回收并供其他代码进行内存申请时候复用,或者将其归还给操作系统,这种针对内存级别资源的自动回收过程,即为垃圾回收。而负责垃圾回收的程序组件,即为垃圾回收器。
当然对于临时的内存泄漏是可以注意一下的。
GC将在另外一篇单独做个说明
标签:泄漏,err,int,goroutine,golang,内存,func,new From: https://www.cnblogs.com/zhanchenjin/p/17098100.html