Keito

© 2024 Keito

技術ブログとポートフォリオ

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

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

概要

  • 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で確認

  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でインストール)