概要
- Go言語を使っていてメモリリークを経験したことがないので、意図的にメモリリークを発生させるサンプルコードを書いてみた
- メモリリークしているかも下記を使用して確認してみた
-
pprof
(Goの標準profiler) -
go tool pprof
コマンドでメモリの使用状況を可視化
-
サンプルコード
package main
import (
"fmt"
"net/http"
_ "net/http/pprof"
"time"
)
// グローバル変数として巨大なスライスへの参照を保持
var leakedData = make([][]byte, 0)
func memoryLeak() {
for {
// 1MBのデータを作成
data := make([]byte, 1024*1024)
// グローバル変数にデータの参照を保持し続ける
leakedData = append(leakedData, data)
// 少し待つ
time.Sleep(100 * time.Millisecond)
}
}
func main() {
// pprof用にHTTPサーバを起動(:6060でアクセス可能)
go func() {
fmt.Println(http.ListenAndServe("localhost:6060", nil))
}()
// メモリリークを発生させる関数を呼び出し
memoryLeak()
}
サンプルコード解説
-
net/http/pprof
をimportすることで、http://localhost:6060/debug/pprof/
にアクセスできるようになる -
memoryLeak()
関数内で1MBのデータを生成し続け、参照をleakedData
に保持する - 本来なら使い終わったら開放されるべきだが、グローバル変数に参照が残ることでGCの対象にならずメモリを消費し続ける
pprofで確認
-
ターミナルでサンプルコード実行
go run main.go
-
別ターミナルで下記コマンドの実行
go tool pprof http://localhost:6060/debug/pprof/heap
-
top
コマンド実行でメモリ食ってる関数を一覧表示- ここで8.1MB全部が memoryLeak 関数内で確保され、解放されずに生き残ってるのがわかる
(pprof) top Showing nodes accounting for 8.10MB, 100% of 8.10MB total flat flat% sum% cum cum% 8.10MB 100% 100% 8.10MB 100% main.memoryLeak (inline) 0 0% 100% 8.10MB 100% main.main 0 0% 100% 8.10MB 100% runtime.main
-
list 関数名
コマンドで行単位の詳細確認(pprof) list main.memoryLeak Total: 8.10MB ROUTINE ======================== main.memoryLeak in /Users/isurugikeito/dev/go/go-demo/demo/low_layer/main.go 8.10MB 8.10MB (flat, cum) 100% of Total . . 13:func memoryLeak() { . . 14: for { . . 15: // 1MBのデータを作成 8.10MB 8.10MB 16: data := make([]byte, 1024*1024) . . 17: . . 18: // グローバル変数にデータの参照を保持し続ける . . 19: leakedData = append(leakedData, data) . . 20: . . 21: // 少し待つ
-
web
コマンドでブラウザ上でグラフで視覚化- 使用するにはGraphvizが必要(Macなら
brew install graphviz
でインストール)
- 使用するにはGraphvizが必要(Macなら