跳到主要内容

内存泄漏的7种场景

数组的错误使用

由于数组是Golang的基本数据类型,每个数组占用不同的内存空间,生命周期互不干扰,很难出现内存泄漏的情况。但是数组作为形参传输时,遵循的是值拷贝,如果函数被多次调用且数组过大时,则会导致内存使用激增

    func countTarget(nums [100000]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的)如果是在高并发场景下每个协程都同时调用该函数,内存占用量是非常恐怖的。因此对于大数组放在形参场景下,通常使用切片或者指针进行传递,避免短时间的内存使用激增。

Goroutine泄漏

实际开发中更多的还是Goroutine引起的内存泄漏,因为Goroutine的创建非常简单,通过关键字go即可创建,由于开发的进度大部分程序猿只会关心代码的功能是否实现,很少会关心Goroutine何时退出。如果Goroutine在执行时被阻塞而无法退出,就会导致Goroutine的内存泄漏,一个Goroutine的最低栈大小为2KB,在高并发的场景下,对内存的消耗也是非常恐怖的

互斥锁未释放

 //协程拿到锁未释放,其他协程获取锁会阻塞
mutex := sync.Mutex{}

for i:=0;i<100;i++{
go func(){
mutex.Lock()
fmt.Println("进行中:",i)
time.Sleep(10*time.Second)
}
}
time.Sleep(10*time.Second)

死锁

 func main(){
m1,m2:= sync.Mutex{},sync.RWMutex{}
go func(){
m1.Lock()
fmt.Println("g1 获取 m1")
time.Sleep(1*time.Second)
m2.Lock()
fmt.Println("g1 获取m2")
}()

go func(){
m2.Lock()
fmt.Println("g2 get m2")
time.Sleep(1 * time.Second)
m1.Lock()
fmt.Println("g2 get m1")
}()
//其余协程获取锁都会失败
go func() {
m1.Lock()
fmt.Println("g3 get m1")
}()
time.Sleep(10 * time.Second)
}

空channel

    func main(){
var c chan int
go func(){
c<-1
fmt.Println("g1 send succeed")
time.Sleep(1*time.Second)
}()
go func(){
<-c
fmt.Println("g2 receive success")
time.Sleep(1*time.Second)
}()

time.Sleep(19*time.Second)
}

能出不能进


func main(){
var c = make(chan int)

//10个协程向channel中写数据
for i:=0;i<10;i++{
go func(){
c<-1
fmt.Println("g1 send succeed")
time.Sleep(1*time.Second)
}()
}
//1个协程丛channel中读数据
go func(){
<-c
fmt.Println("g2 receive success")
time.Sleep(1*time.Second)
}()
//会有写的9个协程阻塞得不到释放
time.Sleep(19*time.Second)
}

能进不能出


func main(){
var c = make(chan int)

//10个协程向channel中读数据
for i:=0;i<10;i++{
go func(){
<-c
fmt.Println("g2 receive success")
time.Sleep(1*time.Second)
}()
}
//1个协程丛channel中写数据
go func(){
c<-1
fmt.Println("g1 send succeed")
time.Sleep(1*time.Second)
}()
//会有写的9个协程阻塞得不到释放
time.Sleep(19*time.Second)
}

定时器的使用

time.Ticker是每隔指定的时间就会向通道内写数据。作为循环触发器,必须调用stop方法才会停止,从而被GC掉,否则会一直占用内存空间。

    func main(){
ticker := time.NewTicker(time.Second*1)
go func(){
for t:=range ticker.C {
fmt.Println("ticker被触发",t)
}
}()
time.Sleep(time.Second*10)
//停止ticker
ticker.Stop()
}