首页 > 系统相关 >golang 内存泄漏总结

golang 内存泄漏总结

时间:2023-02-07 14:34:09浏览次数:60  
标签:泄漏 err int goroutine golang 内存 func new

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

相关文章

  • mongodb对内存的使用
    1.默认的分配策略Startingin3.4,theWiredTigerinternalcache,bydefault,willusethelargerofeither:50%ofRAMminus1GB,or256MB.即(总内存×50%-......
  • golang 线程和系统线程的的区别
    和操作系统的线程调度不同的是,Go调度器并不是用一个硬件定时器而是被Go语言"建筑"本身进行调度的。例如当一个goroutine调用了time.Sleep或者被channel调用或者mutex操作阻......
  • golang 字符串
    字符串常用系统函数1.len(str):统计字符串长度这个函数是内建函数,存在于内建包builtin中,可以不用导入直接使用。golang的编码统一为utf-8(ascii的字符(字母和数字)占一个字......
  • golang defer
    packagemainimport("fmt")//defer的最佳实践是,当函数执行完毕后,可以及时的释放函数创建的资源//在前面先写deferfile.close(),先把defer压入栈不执行,先执行......
  • (转)go语言-golang基础-queue队列和stack堆栈
    原文:https://www.cnblogs.com/malukang/p/12708850.html1.queue队列队列(queue),是一种FIFO(FirstInFirstOut)先进先出的线性表。通常用数据或者链表来实现队列。队......
  • JVM启动速度大页内存验证
    大页内存设置先查看cat/proc/meminfo|grep-ihuge获取大页内存的大小信息.AnonHugePages:42022912kBHugePages_Total:158720HugePages_Free:1005H......
  • 面试必问:说一下 Java 虚拟机的内存布局?
    我们通常所说的Java虚拟机(JVM)的内存布局,一般是指Java虚拟机的运行时数据区(RuntimeDataArea),也就是当字节码被类加载器加载之后的执行区域划分。当然它通常是JVM模块......
  • 【转】Is there a better dependency injection pattern in golang?
     https://coolshell.cn/articles/9949.html https://dotblogs.com.tw/daniel/2018/01/17/140435 https://github.com/golobby/container https://blog.drewolson.......
  • 获取安卓内存状态
    packagecom.itheiima28.memorydemo;importjava.io.File;importandroid.app.Activity;importandroid.os.Bundle;importandroid.os.Environment;importandroid.os.Stat......
  • 保存数据到手机内存(QQ登录保存密码)
    点击记住密码,保存账号密码。1.获取各数据的对象2.判断记住密码是否被选中,如果被选中,存起来3.登录成功显示界面:<spanstyle="font-size:14px;"><LinearLayoutxmlns:andro......