上文说到面试官考察了超超数组和切片的区别,超超答道了切片使用不当会导致内存泄漏的场景。那么除了切片的错误使用会导致内存泄漏,有没有其他场景会导致内存泄漏呢?来看看超超是怎么理解的吧~
数组的错误使用
面试官:在开发中数组使用不当会导致内存泄漏吗?
超超:由于数组是Golang的基本数据类型,每个数组占用不同的内存空间,生命周期互不干扰,很难出现内存泄漏的情况。但是数组作为形参传输时,遵循的是值拷贝,如果函数被多次调用且数组过大时,则会导致内存使用激增。
//统计nums中target出现的次数
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的)如果是在高并发场景下每个协程都同时调用该函数,内存占用量是非常恐怖的。因此对于大数组放在形参场景下,通常使用切片或者指针进行传递,避免短时间的内存使用激增。
Goroutine泄漏
面试官:对于内存泄漏,还会在哪些场景下会触发?
超超:实际开发中更多的还是Goroutine引起的内存泄漏,因为Goroutine的创建非常简单,通过关键字go即可创建,由于开发的进度大部分程序猿只会关心代码的功能是否实现,很少会关心Goroutine何时退出。如果Goroutine在执行时被阻塞而无法退出,就会导致Goroutine的内存泄漏,一个Goroutine的最低栈大小为2KB,在高并发的场景下,对内存的消耗也是非常恐怖的!
互斥锁未释放
1//协程拿到锁未释放,其他协程获取锁会阻塞
2func mutexTest() {
3 mutex := sync.Mutex{}
4 for i := 0; i < 10; i++ {
5 go func() {
6 mutex.Lock()
7 fmt.Printf("%d goroutine get mutex", i)
8 //模拟实际开发中的操作耗时
9 time.Sleep(100 * time.Millisecond)
10 }()
11 }
12 time.Sleep(10 * time.Second)
13}
死锁
1func mutexTest() {
2 m1, m2 := sync.Mutex{}, sync.RWMutex{}
3 //g1得到锁1去获取锁2
4 go func() {
5 m1.Lock()
6 fmt.Println("g1 get m1")
7 time.Sleep(1 * time.Second)
8 m2.Lock()
9 fmt.Println("g1 get m2")
10 }()
11 //g2得到锁2去获取锁1
12 go func() {
13 m2.Lock()
14 fmt.Println("g2 get m2")
15 time.Sleep(1 * time.Second)
16 m1.Lock()
17 fmt.Println("g2 get m1")
18 }()
19 //其余协程获取锁都会失败
20 go func() {
21 m1.Lock()
22 fmt.Println("g3 get m1")
23 }()
24 time.Sleep(10 * time.Second)
25}
空channel
1func channelTest() {
2 //声明未初始化的channel读写都会阻塞
3 var c chan int
4 //向channel中写数据
5 go func() {
6 c <- 1
7 fmt.Println("g1 send succeed")
8 time.Sleep(1 * time.Second)
9 }()
10 //从channel中读数据
11 go func() {
12 <-c
13 fmt.Println("g2 receive succeed")
14 time.Sleep(1 * time.Second)
15 }()
16 time.Sleep(10 * time.Second)
17}
能出不能进
1func channelTest() {
2 var c = make(chan int)
3 //10个协程向channel中写数据
4 for i := 0; i < 10; i++ {
5 go func() {
6 c <- 1
7 fmt.Println("g1 send succeed")
8 time.Sleep(1 * time.Second)
9 }()
10 }
11 //1个协程丛channel中读数据
12 go func() {
13 <-c
14 fmt.Println("g2 receive succeed")
15 time.Sleep(1 * time.Second)
16 }()
17 //会有写的9个协程阻塞得不到释放
18 time.Sleep(10 * time.Second)
19}
能进不能出
1func channelTest() {
2 var c = make(chan int)
3 //10个协程向channel中读数据
4 for i := 0; i < 10; i++ {
5 go func() {
6 <- c
7 fmt.Println("g1 receive succeed")
8 time.Sleep(1 * time.Second)
9 }()
10 }
11 //1个协程丛channel写读数据
12 go func() {
13 c <- 1
14 fmt.Println("g2 send succeed")
15 time.Sleep(1 * time.Second)
16 }()
17 //会有读的9个协程阻塞得不到释放
18 time.Sleep(10 * time.Second)
19}
定时器的使用
面试官:你知道time.Ticker吗?
超超:知道的,time.Ticker是每隔指定的时间就会向通道内写数据。作为循环触发器,必须调用stop方法才会停止,从而被GC掉,否则会一直占用内存空间。
1func tickerTest() {
2 //定义一个ticker,每隔500毫秒触发
3 ticker := time.NewTicker(time.Second * 1)
4 //Ticker触发
5 go func() {
6 for t := range ticker.C {
7 fmt.Println("ticker被触发", t)
8 }
9 }()
10
11 time.Sleep(time.Second * 10)
12 //停止ticker
13 ticker.Stop()
14}
面试官:在开发中你知道哪些工具可以来排查内存泄漏吗?
超超:了解的….

对于Golang的内存泄漏场景欢迎小伙伴在下方留言讨论呀!同样欢迎添加我的微信,进读者群和超超一起讨论开发中的难题!