一,什么是内存泄漏

Go 中的并发性是以 goroutine(独立活动)和 channel(用于通信)的形式实现的。处理 goroutine 时,程序员需要小心翼翼地避免泄露。如果最终永远堵塞在 I/O 上(例如 channel 通信),或者陷入死循环,那么 goroutine 会发生泄露。即使是阻塞的 goroutine,也会消耗资源,因此,程序可能会使用比实际需要更多的内存,或者最终耗尽内存,从而导致崩溃。虽然我们知道goroutine在初始化的时候会会分配一个2kb的栈地址空间,(关于内存分配的问题可以参考http://ifeve.com/memory-barriers-or-fences/)但是如果大量的goroutine被阻塞,造成的内存浪费也是客观的。

我们知道go采用的gc是标记回收法:
从根变量来时遍历所有被引用对象,标记之后进行清除操作,对未标记对象进行回收。
阻塞状态是go调度的一个待唤醒的状态,是不能被gc的。

我们知道:
如向channel发送数据的时候,该goroutine会一直阻塞直到另一个goroutine接受该channel的数据,反之亦然,goroutine接受channel的数据的时候也会一直阻塞直到另一个goroutine向该channel发送数据

发送端的channel满了,那么发送端将处于阻塞状态知道里面的消息被消费

  1. ch := make(chan int)
  2. go func() {
  3. ch <- 1
  4. fmt.Println(111)
  5. }()

接受端消费channel的时候发现为空

  1. ch := make(chan int, 1)
  2. go func() {
  3. <-ch
  4. fmt.Println(111)
  5. }()

上述的两种中状态就是最简单的goroutine阻塞的情况,我们遇到goroutine阻塞,从而导致的内存泄漏的情况无非就是这样的。

内存泄漏是如何产生的呢?

1、发送一个没有接受者的channel

  1. func query() int {
  2. n := rand.Intn(100)
  3. time.Sleep(time.Duration(n) * time.Millisecond)
  4. return n
  5. }
  6.  
  7. func queryAll() int {
  8. ch := make(chan int)
  9. go func() { ch <- query() }()
  10. go func() { ch <- query() }()
  11. go func() { ch <- query() }()
  12. return <-ch
  13. }
  14.  
  15. func main() {
  16. for i := 0; i < 4; i++ {
  17. queryAll()
  18. fmt.Printf("#goroutines: %d", runtime.NumGoroutine())
  19. }
  20. }

输出:

  1. #goroutines: 3
  2. #goroutines: 5
  3. #goroutines: 7
  4. #goroutines: 9

每次调用 queryAll 后,goroutine 的数目会发生增长。问题在于,在接收到第一个响应后,“较慢的” goroutine 将会发送到另一端没有接收者的 channel 中。

2、nil channel

写入到nil channel会永远阻塞

  1. package main
  2.  
  3. func main() {
  4. var ch chan struct{}
  5. ch <- struct{}{}
  6. }

所以它导致死锁:

  1. fatal error: all goroutines are asleep - deadlock!
  2.  
  3. goroutine 1 [chan send (nil chan)]:
  4. main.main()
  5. ...

当从 nil channel 读取数据时,同样的事情发生了:
var ch chan struct{}
<-ch

传递尚未初始化的channel,也可能发生

  1. func main() {
  2. var ch chan int
  3. if false {
  4. ch = make(chan int, 1)
  5. ch <- 1
  6. }
  7. go func(ch chan int) {
  8. <-ch
  9. }(ch)
  10.  
  11. c := time.Tick(1 * time.Second)
  12. for range c {
  13. fmt.Printf("#goroutines: %d", runtime.NumGoroutine())
  14. }
  15. }

3、channel通讯超时

如果goroutine在通讯的时候,发送端的channel由于某种原因没有到达消费端的goroutine,那么下游消费的goroutine就会长时间处于阻塞的状态等待消息的唤醒。

  1. /*
  2. 检查channel读写超时,并做超时的处理
  3. */
  4. func testTimeout() {
  5. g := make(chan int)
  6. quit := make(chan bool)
  7.  
  8. go func() {
  9. for {
  10. select {
  11. case v := <-g:
  12. fmt.Println(v)
  13. case <-time.After(time.Second * time.Duration(3)):
  14. quit <- true
  15. fmt.Println("超时,通知主线程退出")
  16. return
  17. }
  18. }
  19. }()
  20.  
  21. for i := 0; i < 3; i++ {
  22. g <- i
  23. }
  24.  
  25. <-quit
  26. fmt.Println("收到退出通知,主线程退出")
  27. }

二,如何排查内存的泄漏

我们可以使用pprof进行排查

那我们就来了解下pprof的基本知识点

什么是pprof

pprof是Go的性能分析工具,在程序运行过程中,可以记录程序的运行信息,可以是CPU使用情况、内存使用情况、goroutine运行情况等,当需要性能调优或者定位Bug时候,这些记录的信息是相当重要。

基本使用

使用pprof有多种方式,Go已经现成封装好了1个:net/http/pprof,使用简单的几行命令,就可以开启pprof,记录运行信息,并且提供了Web服务,能够通过浏览器和命令行2种方式获取运行数据。

  1. import (
  2. "fmt"
  3. "net/http"
  4. _ "net/http/pprof"
  5. )
  6.  
  7. func main() {
  8. // 开启pprof,监听请求
  9. ip := "127.0.0.1:6060"
  10. if err := http.ListenAndServe(ip, nil); err != nil {
  11. fmt.Printf("start pprof failed on %s\n", ip)
  12. }
  13. }

我们输入ip:port/debug/pprof/打开pprof主页
例如我的地址
http://127.0.0.1:6060/debug/pprof/

会看到下面的信息

下面来分析一下上面的具体参数的意思

  1. allocs:
  2. A sampling of all past memory allocations
  3. 所有过去内存分配的采样
  4. block:
  5. Stack traces that led to blocking on synchronization primitives
  6. 导致同步原语阻塞的堆栈跟踪
  7. cmdline:
  8. The command line invocation of the current program
  9. 当前程序的命令行调用
  10.  
  11. goroutine:
  12. Stack traces of all current goroutines
  13. heap:
  14. A sampling of memory allocations of live objects. You can specify the gc GET parameter to run GC before taking the heap sample.
  15. 活动对象内存分配的采样。在获取堆样本之前,可以指定gc get参数来运行gc。(也就是堆内存的信息)
  16. mutex:
  17. Stack traces of holders of contended mutexes
  18. 争用互斥锁持有者的堆栈跟踪(锁的信息)
  19. profile:
  20. CPU profile. You can specify the duration in the seconds GET parameter. After you get the profile file, use the go tool pprof command to investigate the profile.
  21. CPU配置文件。可以在seconds get参数中指定持续时间。获取配置文件后,使用go tool pprof命令调查配置文件。
  22. threadcreate:
  23. Stack traces that led to the creation of new OS threads
  24. 导致创建新操作系统线程的堆栈跟踪(线程的信息)
  25. trace:
  26. A trace of execution of the current program. You can specify the duration in the seconds GET parameter. After you get the trace file, use the go tool trace command to investigate the trace.
  27. 对当前程序执行的跟踪。可以在seconds get参数中指定持续时间。获取跟踪文件后,使用go tool trace命令调查跟踪。

命令行方式

当连接在服务器终端上的时候,是没有浏览器可以使用的,Go提供了命令行的方式,能够获取以上5类信息,这种方式用起来更方便。
使用命令go tool pprof url可以获取指定的profile文件,此命令会发起http请求,然后下载数据到本地,之后进入交互式模式,就像gdb一样,可以使用命令查看运行信息,以下是5类请求的方式:

  1. # 下载cpu profile,默认从当前开始收集30s的cpu使用情况,需要等待30s
  2. go tool pprof http://localhost:6060/debug/pprof/profile # 30-second CPU profile
  3. go tool pprof http://localhost:6060/debug/pprof/profile?seconds=120 # wait 120s
  4.  
  5. # 下载heap profile
  6. go tool pprof http://localhost:6060/debug/pprof/heap # heap profile
  7.  
  8. # 下载goroutine profile
  9. go tool pprof http://localhost:6060/debug/pprof/goroutine # goroutine profile
  10.  
  11. # 下载block profile
  12. go tool pprof http://localhost:6060/debug/pprof/block # goroutine blocking profile
  13.  
  14. # 下载mutex profile
  15. go tool pprof http://localhost:6060/debug/pprof/mutex

内存泄露的发现

如果使用云平台部署Go程序,云平台都提供了内存查看的工具,可以查看OS的内存占用情况和某个进程的内存占用情况,比如阿里云,我们在1个云主机上只部署了1个Go服务,所以OS的内存占用情况,基本是也反映了进程内存占用情况,OS内存占用情况如下,可以看到随着时间的推进,内存的占用率在不断的提高,这是内存泄露的最明显现象:

怎么用heap发现内存问题
使用pprof的heap能够获取程序运行时的内存信息,在程序平稳运行的情况下,每个一段时间使用heap获取内存的profile,然后使用base能够对比两个profile文件的差别,就像diff命令一样显示出增加和减少的变化,使用一个简单的demo来说明heap和base的使用,依然使用demo2进行展示。

  1. // 展示内存增长和pprof,并不是泄露
  2. package main
  3.  
  4. import (
  5. "fmt"
  6. "net/http"
  7. _ "net/http/pprof"
  8. "os"
  9. "time"
  10. )
  11.  
  12. // 运行一段时间:fatal error: runtime: out of memory
  13. func main() {
  14. // 开启pprof
  15. go func() {
  16. ip := "0.0.0.0:6060"
  17. if err := http.ListenAndServe(ip, nil); err != nil {
  18. fmt.Printf("start pprof failed on %s\n", ip)
  19. os.Exit(1)
  20. }
  21. }()
  22.  
  23. tick := time.Tick(time.Second / 100)
  24. var buf []byte
  25. for range tick {
  26. buf = append(buf, make([]byte, 1024*1024)...)
  27. }
  28. }

将上面代码运行起来,执行以下命令获取profile文件,1分钟后再获取1次。
go tool pprof http://localhost:6060/debug/pprof/heap
我已经获取到了两个profile文件:

  1. Administrator@SC-201807230940 MINGW64 ~/pprof
  2. $ ls
  3. pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.001.pb.gz
  4. pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.002.pb.gz

使用base把001文件作为基准,然后用002和001对比,先执行top看top的对比,然后执行list main列出main函数的内存对比,结果如下:

  1. Administrator@SC-201807230940 MINGW64 ~/pprof
  2. $ go tool pprof -base pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.001.pb.gz pprof.alloc_objects.alloc_space.inuse_objects.inuse_space.002.pb.gz

结果

  1. (pprof) top
  2. Showing nodes accounting for 1.04GB, 50.58% of 2.06GB total
  3. flat flat% sum% cum cum%
  4. 1.04GB 50.58% 50.58% 1.04GB 50.58% main.main
  5. 0 0% 50.58% 1.04GB 50.58% runtime.main
  6. (pprof)

使用traces

  1. Type: inuse_space
  2. Time: Jul 29, 2019 at 8:48am (CST)
  3. -----------+-------------------------------------------------------
  4. bytes: 1.55GB
  5. 1.55GB main.main
  6. runtime.main
  7. -----------+-------------------------------------------------------
  8. bytes: 1.24GB
  9. 0 main.main
  10. runtime.main
  11. -----------+-------------------------------------------------------
  12. bytes: 1016.83MB
  13. 0 main.main
  14. runtime.main
  15. -----------+-------------------------------------------------------
  16. bytes: 813.46MB
  17. 0 main.main
  18. runtime.main
  19. -----------+-------------------------------------------------------
  20. bytes: 902.59kB
  21. 0 compress/flate.NewWriter
  22. compress/gzip.(*Writer).Write
  23. runtime/pprof.(*profileBuilder).build
  24. runtime/pprof.writeHeapProto
  25. runtime/pprof.writeHeap
  26. runtime/pprof.(*Profile).WriteTo
  27. net/http/pprof.handler.ServeHTTP
  28. net/http/pprof.Index
  29. net/http.HandlerFunc.ServeHTTP
  30. net/http.(*ServeMux).ServeHTTP
  31. net/http.serverHandler.ServeHTTP
  32. net/http.(*conn).serve
  33. -----------+-------------------------------------------------------
  34. bytes: 650.77MB
  35. 0 main.main
  36. runtime.main
  37. -----------+-------------------------------------------------------
  38. bytes: 520.61MB
  39. 0 main.main
  40. runtime.main
  41. -----------+-------------------------------------------------------
  42. bytes: 416.48MB
  43. 0 main.main
  44. runtime.main
  45. -----------+-------------------------------------------------------
  46. bytes: 333.19MB
  47. 0 main.main
  48. runtime.main
  49. -----------+-------------------------------------------------------
  50. bytes: 266.55MB
  51. 0 main.main
  52. runtime.main
  53. -----------+-------------------------------------------------------
  54. bytes: 213.23MB
  55. 0 main.main
  56. runtime.main
  57. -----------+-------------------------------------------------------
  58. bytes: 170.59MB
  59. 0 main.main
  60. runtime.main
  61. -----------+-------------------------------------------------------
  62. bytes: 136.47MB
  63. 0 main.main
  64. runtime.main
  65. -----------+-------------------------------------------------------
  66. bytes: 109.17MB
  67. 0 main.main
  68. runtime.main
  69. -----------+-------------------------------------------------------
  70. bytes: 87.34MB
  71. 0 main.main
  72. runtime.main
  73. -----------+-------------------------------------------------------
  74. bytes: 69.87MB
  75. 0 main.main
  76. runtime.main
  77. -----------+-------------------------------------------------------
  78. bytes: 55.89MB
  79. 0 main.main
  80. runtime.main
  81. -----------+-------------------------------------------------------
  82. bytes: 44.71MB
  83. 0 main.main
  84. runtime.main
  85. -----------+-------------------------------------------------------
  86. bytes: 35.77MB
  87. 0 main.main
  88. runtime.main
  89. -----------+-------------------------------------------------------
  90. bytes: 28.61MB
  91. 0 main.main
  92. runtime.main
  93. -----------+-------------------------------------------------------
  94. bytes: 22.88MB
  95. 0 main.main
  96. runtime.main
  97. -----------+-------------------------------------------------------
  98. bytes: 18.30MB
  99. 0 main.main
  100. runtime.main
  101. -----------+-------------------------------------------------------
  102. bytes: 14.64MB
  103. 0 main.main
  104. runtime.main
  105. -----------+-------------------------------------------------------
  106. bytes: 11.71MB
  107. 0 main.main
  108. runtime.main
  109. -----------+-------------------------------------------------------
  110. bytes: 9.37MB
  111. 0 main.main
  112. runtime.main
  113. -----------+-------------------------------------------------------
  114. bytes: 7.49MB
  115. 0 main.main
  116. runtime.main
  117. -----------+-------------------------------------------------------
  118. bytes: 5.99MB
  119. 0 main.main
  120. runtime.main
  121. -----------+-------------------------------------------------------
  122. bytes: 4.79MB
  123. 0 main.main
  124. runtime.main
  125. -----------+-------------------------------------------------------
  126. bytes: 3.07MB
  127. 0 main.main
  128. runtime.main
  129. -----------+-------------------------------------------------------
  130. bytes: 2.46MB
  131. 0 main.main
  132. runtime.main
  133. -----------+-------------------------------------------------------
  134. bytes: 1.16MB
  135. 0 main.main
  136. runtime.main
  137. -----------+-------------------------------------------------------
  138. bytes: 1MB
  139. 1.16MB main.main
  140. runtime.main
  141. -----------+-------------------------------------------------------
  142. bytes: 520.61MB
  143. -520.61MB main.main
  144. runtime.main
  145. -----------+-------------------------------------------------------
  146. bytes: 416.48MB
  147. 0 main.main
  148. runtime.main
  149. -----------+-------------------------------------------------------
  150. bytes: 333.19MB
  151. 0 main.main
  152. runtime.main
  153. -----------+-------------------------------------------------------
  154. bytes: 266.55MB
  155. 0 main.main
  156. runtime.main
  157. -----------+-------------------------------------------------------
  158. bytes: 213.23MB
  159. 0 main.main
  160. runtime.main
  161. -----------+-------------------------------------------------------
  162. bytes: 170.59MB
  163. 0 main.main
  164. runtime.main
  165. -----------+-------------------------------------------------------
  166. bytes: 136.47MB
  167. 0 main.main
  168. runtime.main
  169. -----------+-------------------------------------------------------
  170. bytes: 109.17MB
  171. 0 main.main
  172. runtime.main
  173. -----------+-------------------------------------------------------
  174. bytes: 87.34MB
  175. 0 main.main
  176. runtime.main
  177. -----------+-------------------------------------------------------
  178. bytes: 69.87MB
  179. 0 main.main
  180. runtime.main
  181. -----------+-------------------------------------------------------
  182. bytes: 55.89MB
  183. 0 main.main
  184. runtime.main
  185. -----------+-------------------------------------------------------
  186. bytes: 44.71MB
  187. 0 main.main
  188. runtime.main
  189. -----------+-------------------------------------------------------
  190. bytes: 35.77MB
  191. 0 main.main
  192. runtime.main
  193. -----------+-------------------------------------------------------
  194. bytes: 28.61MB
  195. 0 main.main
  196. runtime.main
  197. -----------+-------------------------------------------------------
  198. bytes: 22.88MB
  199. 0 main.main
  200. runtime.main
  201. -----------+-------------------------------------------------------
  202. bytes: 18.30MB
  203. 0 main.main
  204. runtime.main
  205. -----------+-------------------------------------------------------
  206. bytes: 14.64MB
  207. 0 main.main
  208. runtime.main
  209. -----------+-------------------------------------------------------
  210. bytes: 11.71MB
  211. 0 main.main
  212. runtime.main
  213. -----------+-------------------------------------------------------
  214. bytes: 9.37MB
  215. 0 main.main
  216. runtime.main
  217. -----------+-------------------------------------------------------
  218. bytes: 7.49MB
  219. 0 main.main
  220. runtime.main
  221. -----------+-------------------------------------------------------
  222. bytes: 5.99MB
  223. 0 main.main
  224. runtime.main
  225. -----------+-------------------------------------------------------
  226. bytes: 4.79MB
  227. 0 main.main
  228. runtime.main
  229. -----------+-------------------------------------------------------
  230. bytes: 3.07MB
  231. 0 main.main
  232. runtime.main
  233. -----------+-------------------------------------------------------
  234. bytes: 2.46MB
  235. 0 main.main
  236. runtime.main
  237. -----------+-------------------------------------------------------
  238. bytes: 1.16MB
  239. 0 main.main
  240. runtime.main
  241. -----------+-------------------------------------------------------
  242. bytes: 1MB
  243. -1.16MB main.main
  244. runtime.main
  245. -----------+-------------------------------------------------------
  246. (pprof)
  1. 使用list
  1. (pprof) list main.main
  2. Total: 2.06GB
  3. ROUTINE ======================== main.main in D:\gowork\src\study\main\main.go
  4. 1.04GB 1.04GB (flat, cum) 50.58% of Total
  5. . . 57: }()
  6. . . 58:
  7. . . 59: tick := time.Tick(time.Second / 100)
  8. . . 60: var buf []byte
  9. . . 61: for range tick {
  10. 1.04GB 1.04GB 62: buf = append(buf, make([]byte, 1024*1024)...)
  11. . . 63: }
  12. . . 64:}
  13. . . 65:
  14. . . 66:
  15. . . 67:
  16. (pprof)

heap“不能”定位内存泄露
heap能显示内存的分配情况,以及哪行代码占用了多少内存,我们能轻易的找到占用内存最多的地方,如果这个地方的数值还在不断怎大,基本可以认定这里就是内存泄露的位置。
曾想按图索骥,从内存泄露的位置,根据调用栈向上查找,总能找到内存泄露的原因,这种方案看起来是不错的,但实施起来却找不到内存泄露的原因,结果是事半功倍。
原因在于一个Go程序,其中有大量的goroutine,这其中的调用关系也许有点复杂,也许内存泄露是在某个三方包里。举个栗子,比如下面这幅图,每个椭圆代表1个goroutine,其中的数字为编号,箭头代表调用关系。heap profile显示g111(最下方标红节点)这个协程的代码出现了泄露,任何一个从g101到g111的调用路径都可能造成了g111的内存泄露,有2类可能:
该goroutine只调用了少数几次,但消耗了大量的内存,说明每个goroutine调用都消耗了不少内存,内存泄露的原因基本就在该协程内部。
该goroutine的调用次数非常多,虽然每个协程调用过程中消耗的内存不多,但该调用路径上,协程数量巨大,造成消耗大量的内存,并且这些goroutine由于某种原因无法退出,占用的内存不会释放,内存泄露的原因在到g111调用路径上某段代码实现有问题,造成创建了大量的g111。
第2种情况,就是goroutine泄露,这是通过heap无法发现的,所以heap在定位内存泄露这件事上,发挥的作用不大。

内存泄露的排查

Web可视化查看

Web方式适合web服务器的端口能访问的情况,使用起来方便,有2种方式:
查看某条调用路径上,当前阻塞在此goroutine的数量
查看所有goroutine的运行栈(调用路径),可以显示阻塞在此的时间

  1. import (
  2. "fmt"
  3. "net/http"
  4. _ "net/http/pprof"
  5. "os"
  6. "time"
  7. )
  8.  
  9. func main() {
  10. // 开启pprof
  11. go func() {
  12. ip := "0.0.0.0:6060"
  13. if err := http.ListenAndServe(ip, nil); err != nil {
  14. fmt.Printf("start pprof failed on %s\n", ip)
  15. os.Exit(1)
  16. }
  17. }()
  18. outCh := make(chan int)
  19. for i := 1; i <= 5; i++ {
  20. go func() {
  21. outCh <- 1
  22. }()
  23. time.Sleep(time.Second)
  24. }
  25.  
  26. ///value := <-outCh
  27. //fmt.Println("value : ", value)
  28. //time
  29. time.Sleep(100 * time.Second)
  30. }

方式一

url请求中设置debug=1:

使用http://127.0.0.1:6060/debug/pprof/goroutine?debug=1

我们可以明显的看到有5个goroutine被阻塞了

其实应该是5个地方主goroutine应为时间也是被阻塞掉了

我们看到有5个goroutine被同一个资源阻塞掉了,并且指向的代码块是23行,那么我们就能很快的进行定位排查。

方式二

url请求中设置debug=2:
使用:http://127.0.0.1:6060/debug/pprof/goroutine?debug=2

我们可以看到阻塞的时间,同时也能看到阻塞的代码块

命令行交互式方法

top会列出5个统计数据:
flat: 本函数占用的内存量。
flat%: 本函数内存占使用中内存总量的百分比。
sum%: 前面每一行flat百分比的和,比如第2行虽然的100% 是 100% + 0%。
cum: 是累计量,加入main函数调用了函数f,函数f占用的内存量,也会记进来。
cum%: 是累计量占总量的百分比。
list
查看某个函数的代码,以及该函数每行代码的指标信息,如果函数名不明确,会进行模糊匹配,比如list main会列出main.main和runtime.main。

traces
打印所有调用栈,以及调用栈的指标信息。

下面是具体的排查流程
1、使用top

  1. $ go tool pprof http://0.0.0.0:6060/debug/pprof/goroutine
  2. Fetching profile over HTTP from http://0.0.0.0:6060/debug/pprof/goroutine
  3. Saved profile in C:\Users\Administrator\pprof\pprof.goroutine.006.pb.gz
  4. Type: goroutine
  5. Time: Jul 29, 2019 at 8:06am (CST)
  6. Entering interactive mode (type "help" for commands, "o" for options)
  7. (pprof) top
  8. Unrecognized command: "\x1b[A\x1b[Btop"
  9. (pprof) top
  10. Showing nodes accounting for 9, 100% of 9 total
  11. Showing top 10 nodes out of 32
  12. flat flat% sum% cum cum%
  13. 7 77.78% 77.78% 7 77.78% runtime.gopark
  14. 1 11.11% 88.89% 1 11.11% net/http.(*connReader).backgroundRead
  15. 1 11.11% 100% 1 11.11% runtime/pprof.writeRuntimeProfile
  16. 0 0% 100% 1 11.11% internal/poll.(*FD).Accept
  17. 0 0% 100% 1 11.11% internal/poll.(*FD).acceptOne
  18. 0 0% 100% 1 11.11% internal/poll.(*ioSrv).ExecIO
  19. 0 0% 100% 1 11.11% internal/poll.(*pollDesc).wait
  20. 0 0% 100% 1 11.11% internal/poll.runtime_pollWait
  21. 0 0% 100% 1 11.11% main.main
  22. 0 0% 100% 1 11.11% main.main.func1
  23. (pprof)

我们可以看到有7个被阻塞了

我们通过traces打印出具体的调用链路

  1. (pprof) traces
  2. Type: goroutine
  3. Time: Jul 29, 2019 at 8:06am (CST)
  4. -----------+-------------------------------------------------------
  5. 5 runtime.gopark
  6. runtime.goparkunlock
  7. runtime.chansend
  8. runtime.chansend1
  9. main.main.func2
  10. -----------+-------------------------------------------------------
  11. 1 runtime.gopark
  12. runtime.netpollblock
  13. internal/poll.runtime_pollWait
  14. internal/poll.(*pollDesc).wait
  15. internal/poll.(*ioSrv).ExecIO
  16. internal/poll.(*FD).acceptOne
  17. internal/poll.(*FD).Accept
  18. net.(*netFD).accept
  19. net.(*TCPListener).accept
  20. net.(*TCPListener).AcceptTCP
  21. net/http.tcpKeepAliveListener.Accept
  22. net/http.(*Server).Serve
  23. net/http.(*Server).ListenAndServe
  24. net/http.ListenAndServe
  25. main.main.func1
  26. -----------+-------------------------------------------------------
  27. 1 runtime.gopark
  28. runtime.goparkunlock
  29. time.Sleep
  30. main.main
  31. runtime.main
  32. -----------+-------------------------------------------------------
  33. 1 net/http.(*connReader).backgroundRead
  34. -----------+-------------------------------------------------------
  35. 1 runtime/pprof.writeRuntimeProfile
  36. runtime/pprof.writeGoroutine
  37. runtime/pprof.(*Profile).WriteTo
  38. net/http/pprof.handler.ServeHTTP
  39. net/http/pprof.Index
  40. net/http.HandlerFunc.ServeHTTP
  41. net/http.(*ServeMux).ServeHTTP
  42. net/http.serverHandler.ServeHTTP
  43. net/http.(*conn).serve
  44. -----------+-------------------------------------------------------
  45. (pprof)

我们可以看到5个被阻塞到了 main.main.func2

然后我们可以使用list查看具体代码的阻塞

  1. (pprof) list main.main.func2
  2. Total: 9
  3. ROUTINE ======================== main.main.func2 in D:\gowork\src\study\main\main.go
  4. 0 5 (flat, cum) 55.56% of Total
  5. . . 29: }
  6. . . 30: }()
  7. . . 31: outCh := make(chan int)
  8. . . 32: for i := 1; i <= 5; i++ {
  9. . . 33: go func() {
  10. . 5 34: outCh <- 1
  11. . . 35: }()
  12. . . 36: time.Sleep(time.Second)
  13. . . 37: }
  14. . . 38:
  15. . . 39: ///value := <-outCh
  16. (pprof)

我们可以看到已经将我们的代码阻塞块给打印出来了,我们就能很好的进行排查了。

参考:https://studygolang.com/articles/20529

参考:https://studygolang.com/articles/20519

参考:https://segmentfault.com/a/1190000019222661

go中内存泄露的发现与排查的更多相关文章

  1. java中内存泄露有几种?如何分析泄露原因

    一.Java内存回收机制 不论哪种语言的内存分配方式,都需要返回所分配内存的真实地址,也就是返回一个指针到内存块的首地址.Java中对象是采用new或者反射的方法创建的,这些对象的创建都是在堆(Hea ...

  2. Android中内存泄露与如何有效避免OOM总结

    一.关于OOM与内存泄露的概念 我们在Android开发过程中经常会遇到OOM的错误,这是因为我们在APP中没有考虑dalvik虚拟机内存消耗的问题. 1.什么是OOM OOM:即OutOfMemoe ...

  3. Qt中内存泄露和退出崩溃的问题 delete

    Qt中帮程序员做了一些内存回收的事情,但正因为这些反而让对此不熟悉的人会屡屡犯错. 收录一篇不错的文章: 在C++中学习过程中,我们都知道: delete 和 new 必须 配对使用(一 一对应):d ...

  4. Java中内存泄露及垃圾回收机制

    转自:http://blog.sina.com.cn/s/blog_538b279a0100098d.html 写的相当不错滴...................... 摘  要 Java语言中,内 ...

  5. C++中内存泄露的检测

    C++没有java的内存垃圾回收机制,在程序短的时候可能比较容易发现问题,在程序长的时候是否有什么检测的方法呢? 假设有一个函数可以某点检测程序的内存使用情况,那是否可以在程序开始的时候设置一个点,在 ...

  6. Windows系统中内存泄露与检测工具及方法

    1.检测需要使用的工具:windbg工具.检测前,需要先安装windbg工具.安装了该工具后,会在安装目录下有一个umdh工具.假设windbg安装在以下目录下:D:\Program Files\De ...

  7. java中内存泄露和内存溢出

    内存溢出 out of memory,是指程序在申请内存时,没有足够的内存空间供其使用,出现out of memory:比如申请了一个integer,但给它存了long才能存下的数,那就是内存溢出. ...

  8. Qt中内存泄露和退出崩溃的问题

    http://blog.csdn.net/wangkuiyun/article/details/7412379

  9. 【微信小程序项目实践总结】30分钟从陌生到熟悉 web app 、native app、hybrid app比较 30分钟ES6从陌生到熟悉 【原创】浅谈内存泄露 HTML5 五子棋 - JS/Canvas 游戏 meta 详解,html5 meta 标签日常设置 C#中回滚TransactionScope的使用方法和原理

    [微信小程序项目实践总结]30分钟从陌生到熟悉 前言 我们之前对小程序做了基本学习: 1. 微信小程序开发07-列表页面怎么做 2. 微信小程序开发06-一个业务页面的完成 3. 微信小程序开发05- ...

随机推荐

  1. linux下查看系统版本

    工作中我们会遇到安装软件需要知道linux是什么发行版本,话不多话上干货(按照我认为常用排序) 1. lsb_release -a 名词解释:LSB (Linux Standard Base) # 如 ...

  2. Java抽象类和接口的区别及联系

    抽象类 注:先将抽象类中的两种方法解释完,再综合解释抽象类 抽象方法 应用场景:其下所有子类都应该有该方法但是大部分子类具体的执行步骤是有所不同的. 必须重写:也可以说"必须实现" ...

  3. 源码分析 Alibaba sentinel 滑动窗口实现原理(文末附原理图)

    要实现限流.熔断等功能,首先要解决的问题是如何实时采集服务(资源)调用信息.例如将某一个接口设置的限流阔值 1W/tps,那首先如何判断当前的 TPS 是多少?Alibaba Sentinel 采用滑 ...

  4. 039.集群网络-Pod和SVC网络实践

    一 Pod和SVC网络 1.1 实践准备及原理 Docker实现了不同的网络模式,Kubernetes也以一种不同的方式来解决这些网络模式的挑战.本完整实验深入剖析Kubernetes在网络层是如何实 ...

  5. python-pathlib

    2019-12-12 04:27:17 我们知道在不同的操作系统中文件路径的组成方式是不同的,因此在python中关于路径的问题以往我们通常采用os.path.join来进行路径的字符串级别的串联,通 ...

  6. 动态网站项目(Dynamic Web Project)CRUD(增删改查)功能的实现(mvc(五层架构)+jdbc+servlet+tomcat7.0+jdk1.8),前端使用JSP+JSTL+EL组合

    代码分享链接 https://pan.baidu.com/s/1UM0grvpttHW9idisiqa6rA    提取码:hx7c 图示           项目结构 1.SelectAllUser ...

  7. [简单路径] Useful Decomposition

    Ramesses knows a lot about problems involving trees (undirected connected graphs without cycles)! He ...

  8. 采用vue编写的功能强大的swagger-ui页面

    think-swagger-ui-vuele swagger-ui有非常多的版本,觉得不太好用,用postman,每个接口都要自己进行录入.所以在基于think-vuele进行了swagger格式js ...

  9. codeforces 1236 A. Bad Ugly Numbers

    A. Bad Ugly Numbers time limit per test 1 second memory limit per test 256 megabytes input standard ...

  10. 一夜搞懂 | JVM 类加载机制

    前言 本文已经收录到我的Github个人博客,欢迎大佬们光临寒舍: 我的GIthub博客 学习导图 一.为什么要学习类加载机制? 今天想跟大家唠嗑唠嗑Java的类加载机制,这是Java的一个很重要的创 ...