[golang]golang time.After内存泄露问题分析

释放双眼,带上耳机,听听看~!

无意中看到一篇文章说,当在for循环里使用select + time.After的组合时会产生内存泄露,于是进行了复现和验证,以此记录

内存泄露复现

问题复现测试代码如下所示:

 1 package main
 2 
 3 import (
 4     \"time\"
 5     )
 6 
 7 func main()  {
 8     ch := make(chan int, 10)
 9 
10     go func() {
11         var i = 1
12         for {
13             i++
14             ch <- i
15         }
16     }()
17 
18     for {
19         select {
20         case x := <- ch:
21             println(x)
22         case <- time.After(3 * time.Minute):
23             println(time.Now().Unix())
24         }
25     }
26 }

执行go run test_time.go,通过top命令,我们可以看到该小程序的内存一直飙升,一小会就能占用3G多内存,如下图:

[golang]golang time.After内存泄露问题分析

原因分析

 在for循环每次select的时候,都会实例化一个一个新的定时器。该定时器在3分钟后,才会被激活,但是激活后已经跟select无引用关系,被gc给清理掉。
换句话说,被遗弃的time.After定时任务还是在时间堆里面,定时任务未到期之前,是不会被gc清理的。

也就是说每次循环实例化的新定时器对象需要3分钟才会可能被GC清理掉,如果我们把上面复现代码中的3分钟改小点,改成10秒钟,通过top命令会发现大概10秒钟后,该程序占用的内存增长到1.05G后基本上就不增长了

原理验证

通过runtime.MemStats可以看到程序中产生的对象数量,我们可以验证一下上面的原理

验证代码如下所示:

 1 package main
 2 
 3 import (
 4     \"time\"
 5     \"runtime\"
 6     \"fmt\"
 7     )
 8 
 9 func main()  {
10     var ms runtime.MemStats
11     runtime.ReadMemStats(&ms)
12     fmt.Println(\"before, have\", runtime.NumGoroutine(), \"goroutines,\", ms.Alloc, \"bytes allocated\", ms.HeapObjects, \"heap object\")
13     for i := 0; i < 1000000; i++ {
14         time.After(3 * time.Minute)
15     }
16     runtime.GC()
17     runtime.ReadMemStats(&ms)
18     fmt.Println(\"after, have\", runtime.NumGoroutine(), \"goroutines,\", ms.Alloc, \"bytes allocated\", ms.HeapObjects, \"heap object\")
19 
20     time.Sleep(10 * time.Second)
21     runtime.GC()
22     runtime.ReadMemStats(&ms)
23     fmt.Println(\"after 10sec, have\", runtime.NumGoroutine(), \"goroutines,\", ms.Alloc, \"bytes allocated\", ms.HeapObjects, \"heap object\")
24 
25     time.Sleep(3 * time.Minute)
26     runtime.GC()
27     runtime.ReadMemStats(&ms)
28     fmt.Println(\"after 3min, have\", runtime.NumGoroutine(), \"goroutines,\", ms.Alloc, \"bytes allocated\", ms.HeapObjects, \"heap object\")
29 }

验证结果如下图所示:

[golang]golang time.After内存泄露问题分析

从图中可以看出,实例中循环跑完后,创建了3000152个对象,由于每个time定时器设置的为3分钟,在3分钟后,可以看到对象都被GC回收,只剩153个对象,从而验证了,time.After定时器在定时任务到达之前,会一直存在于时间堆中,不会释放资源,直到定时任务时间到达后才会释放资源。

问题解决

综上,在go代码中,在for循环里不要使用select + time.After的组合,可以使用time.NewTimer替代

示例代码如下所示:

 1 package main
 2 
 3 import (
 4     \"time\"
 5     )
 6 
 7 func main()  {
 8     ch := make(chan int, 10)
 9 
10     go func() {
11         for {
12             ch <- 100
13         }
14     }()
15 
16     idleDuration := 3 * time.Minute
17     idleDelay := time.NewTimer(idleDuration)
18     defer idleDelay.Stop()
19 
20     for {
21         idleDelay.Reset(idleDuration)
22 
23         select {
24             case x := <- ch:
25                 println(x)
26             case <-idleDelay.C:
27                 return
28             }
29     }
30 }

结果如下图所示:

[golang]golang time.After内存泄露问题分析

从图中可以看到该程序的内存不会再一直增长

参考文章

(1) 分析golang time.After引起内存暴增OOM问题

给TA打赏
共{{data.count}}人
人已打赏
随笔日记

ElasticStack学习(五):ElasticSearch索引与分词

2020-11-9 5:50:44

随笔日记

ABP开发框架前后端开发系列---(15)ABP框架的服务端和客户端缓存的使用,Winform里面的缓存使用,使用ConcurrentDictionary替代Hashtable对多线程的对象缓存处理,在.NET项目中使用PostSharp,使用MemoryCache实现缓存的处理,.NET缓存框架CacheManager在混合式开发框架中的应用(1)-CacheManager的介绍和使用,在.NET项目中使用PostSharp,使用CacheManager实现多种缓存框架的处理,在Winform开发框架中

2020-11-9 5:50:46

0 条回复 A文章作者 M管理员
    暂无讨论,说说你的看法吧
个人中心
购物车
优惠劵
今日签到
有新私信 私信列表
搜索