【Go】わざとメモリリークするサンプルコードと検出方法

更新日:2025/5/3/(土) 03:59

タグ:Go

概要

サンプルコード

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() }

サンプルコード解説

pprofで確認

  1. ターミナルでサンプルコード実行

    1. go run main.go
  2. 別ターミナルで下記コマンドの実行

    1. go tool pprof http://localhost:6060/debug/pprof/heap
  3. topコマンド実行でメモリ食ってる関数を一覧表示

    1. ここで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
  4. 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: // 少し待つ
  5. web コマンドでブラウザ上でグラフで視覚化

    1. 使用するにはGraphvizが必要(Macならbrew install graphvizでインストール)