Upgrade to Pro — share decks privately, control downloads, hide ads and more …

歴史から学ぶ、Goのメモリ管理基礎

 歴史から学ぶ、Goのメモリ管理基礎

2026/1/9 BuriKaigi 2026にて登壇した際の資料です。

Avatar for Takuto Nagami

Takuto Nagami

January 09, 2026
Tweet

More Decks by Takuto Nagami

Other Decks in Technology

Transcript

  1. 学習プロセス: Why, What and How Why なぜ必要 なのか What 何を追加

    するのか アプリケーションに新機能を追加するとき
  2. 学習プロセス: Why, What and How Why なぜ必要 なのか What 何を追加

    するのか How どのように 実装するのか アプリケーションに新機能を追加するとき
  3. アプリケーションに新機能を追加するとき 学習プロセス: Why, What and How Why なぜ必要 なのか What

    何を追加 するのか How どのように 実装するのか 既存セッションは メモリ管理がどのように 実装されているかに フォーカスしがち
  4. 学習プロセス: Why, What and How Why なぜ必要 なのか What 何を追加

    するのか How どのように 実装するのか アプリケーションに新機能を追加するとき
  5. 前提: Goが提供するコンポーネント ソース コード 実行 ファイル go build • コンパイラ

    ◦ ソースコードを実行ファイル (バイナリ) にする • ランタイム ◦ 実行中、書いたソースコードと一緒に動く
  6. 前提: Goが提供するコンポーネント コンパイラ の仕事 • コンパイラ ◦ ソースコードを実行ファイル (バイナリ) にする

    • ランタイム ◦ 実行中、書いたソースコードと一緒に動く ソース コード 実行 ファイル go build
  7. 前提: Goが提供するコンポーネント (バイナリを実行) • コンパイラ ◦ ソースコードを実行ファイル (バイナリ) にする •

    ランタイム ◦ 実行中、書いたソースコードと一緒に動く コンパイラ の仕事 ソース コード 実行 ファイル go build
  8. 前提: Goが提供するコンポーネント ランタイム の仕事 • コンパイラ ◦ ソースコードを実行ファイル (バイナリ) にする

    • ランタイム ◦ 実行中、書いたソースコードと一緒に動く コンパイラ の仕事 ソース コード 実行 ファイル go build (バイナリを実行)
  9. 前提: Goが提供するコンポーネント go run • コンパイラ ◦ ソースコードを実行ファイル (バイナリ) にする

    • ランタイム ◦ 実行中、書いたソースコードと一緒に動く コンパイラ の仕事 ソース コード 実行 ファイル go build (バイナリを実行) ランタイム の仕事
  10. メモリを図解する . . . • メモリはアプリケーションから Key-Valueストアとして使用 ◦ Key: メモリアドレス

    ◦ Value: 各アドレスに1バイト ※ OSはRAMを分割し、それぞれの プロセスに「仮想メモリ」として割り当てる (詳しくは「仮想メモリ」で検索) 0x0000 0x0001 0x0002 0x0003
  11. データの抽象化 - 変数 . . . . . . .

    . . . . {a addr} 12 a (変数) 12
  12. データの抽象化 - 変数 a (変数) 12 b 34 {a addr}

    {b addr} . . . . . . . . . 12 34 プログラマは 変数の名前だけ 認識すれば良い
  13. スタックフレーム • メモリ内における「関数」の表現方法 ◦ それぞれのフレームがスコープになる • 構造はABIによって定められている ◦ ローカル変数 ◦

    (一部) 引数 ◦ 自分を呼び出した関数のスタックフレームアドレス ◦ 別関数を呼び出した場所 などが格納される a() stack frame • 10行目で呼び出し • main()にreturn 5 7
  14. . . . main() stack frame • 3行目で呼び出し 5 スタック

    メモリ領域 {main() s.f. addr} {v addr}
  15. . . . main() stack frame • 3行目で呼び出し 5 a()

    stack frame スタック メモリ領域 {a() s.f. addr} {main() s.f. addr} {v addr}
  16. . . . main() stack frame • 3行目で呼び出し 5 a()

    stack frame • main()にreturn スタック メモリ領域 {a() s.f. addr} {main() s.f. addr} {v addr}
  17. . . . main() stack frame • 3行目で呼び出し 5 a()

    stack frame • main()にreturn 5 スタック メモリ領域 {a() s.f. addr} {arg addr} {main() s.f. addr} {v addr}
  18. . . . main() stack frame • 3行目で呼び出し 5 a()

    stack frame • main()にreturn 5 arg+2= 7 スタック メモリ領域 {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr}
  19. . . . main() stack frame • 3行目で呼び出し 5 a()

    stack frame • main()にreturn 5 7 スタック メモリ領域 {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr}
  20. . . . main() stack frame • 3行目で呼び出し 5 a()

    stack frame • main()にreturn 5 7 スタック メモリ領域 {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr}
  21. . . . main() stack frame • 3行目で呼び出し 5 a()

    stack frame • 10行目で呼び出し • main()にreturn 5 7 スタック メモリ領域 {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr}
  22. . . スタック メモリ領域 main() stack frame • 3行目で呼び出し 5

    a() stack frame • 10行目で呼び出し • main()にreturn {b() s.f. addr} {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 b() stack frame
  23. . . スタック メモリ領域 main() stack frame • 3行目で呼び出し 5

    a() stack frame • 10行目で呼び出し • main()にreturn {b() s.f. addr} {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 b() stack frame • a()にreturn
  24. . . スタック メモリ領域 main() stack frame • 3行目で呼び出し 5

    a() stack frame • 10行目で呼び出し • main()にreturn {b() s.f. addr} {arg addr} {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 b() stack frame • a()にreturn 7
  25. . . スタック メモリ領域 main() stack frame • 3行目で呼び出し 5

    a() stack frame • 10行目で呼び出し • main()にreturn {b() s.f. addr} {arg addr} {b addr} {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 b() stack frame • a()にreturn 7 1
  26. . . スタック メモリ領域 main() stack frame • 3行目で呼び出し 5

    a() stack frame • 10行目で呼び出し • main()にreturn {b() s.f. addr} {arg addr} {b addr} {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 b() stack frame • a()にreturn 7 1
  27. . . スタック メモリ領域 main() stack frame • 3行目で呼び出し 5

    a() stack frame • 10行目で呼び出し • main()にreturn {b() s.f. addr} {arg addr} {b addr} {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 b() stack frame • a()にreturn 7 1 arg+b= 8 (CPUレジスタ)
  28. . . スタック メモリ領域 main() stack frame • 3行目で呼び出し 5

    a() stack frame • 10行目で呼び出し • main()にreturn {b() s.f. addr} {arg addr} {b addr} {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 b() stack frame • a()にreturn 7 1 8 (CPUレジスタ)
  29. . . . スタック メモリ領域 main() stack frame • 3行目で呼び出し

    5 a() stack frame • main()にreturn {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 8 (CPUレジスタ)
  30. . . . スタック メモリ領域 main() stack frame • 3行目で呼び出し

    5 a() stack frame • main()にreturn {a() s.f. addr} {arg addr} {a addr} {main() s.f. addr} {v addr} 5 7 8 (CPUレジスタ)
  31. . . . スタック メモリ領域 main() stack frame 5 {main()

    s.f. addr} {v addr} 8 (CPUレジスタ)
  32. . . . スタック メモリ領域 main() stack frame 8 {main()

    s.f. addr} {v addr} 8 (CPUレジスタ)
  33. ヒープ メモリ領域 . . . . . . . []

    {slice_h addr} {main() s.f. addr} main() stack frame
  34. ヒープ メモリ領域 . . . . . . . []

    {slice_h addr} {main() s.f. addr} main() stack frame ランタイムに よって アドレスが決まる
  35. ヒープ メモリ領域 . . . . . . . []

    {slice_h addr} {main() s.f. addr} main() stack frame main()はsliceが どこかわからない (スタックフレームの中 しか見られない)
  36. . . . . . . . [] {slice_h addr}

    {main() s.f. addr} {slice addr} main() stack frame ヒープ メモリ領域 {slice_h addr}
  37. . . . . . . . [] {slice_h addr}

    {main() s.f. addr} {slice addr} main() stack frame ヒープ メモリ領域 これが ポインタ {slice_h addr}
  38. . . . . . . . [] {slice_h addr}

    {main() s.f. addr} {slice addr} {num addr} main() stack frame ヒープ メモリ領域 5 これが ポインタ {slice_h addr}
  39. . . . main() stack frame • 3行目で呼び出し 5 値渡し

    f() stack frame {f() s.f. addr} {main() s.f. addr} {num addr}
  40. . . . main() stack frame • 3行目で呼び出し 5 値渡し

    f() stack frame 5 {f() s.f. addr} {num addr} {main() s.f. addr} {num addr}
  41. {f() s.f. addr} {num addr} {main() s.f. addr} {num addr}

    . . . main() stack frame • 3行目で呼び出し 5 値渡し f() stack frame 10
  42. {f() s.f. addr} {num addr} {main() s.f. addr} {num addr}

    . . . main() stack frame • 3行目で呼び出し 5 値渡し f() stack frame 10 main()関数の numには 影響しない
  43. . . . ポインタ渡し main() stack frame • 3行目で呼び出し 5

    f() stack frame {f() s.f. addr} {main() s.f. addr} {num addr}
  44. . . . main() stack frame • 3行目で呼び出し f() stack

    frame ポインタ渡し 5 {num addr} {f() s.f. addr} {num addr} {main() s.f. addr} {num addr}
  45. . . . main() stack frame • 3行目で呼び出し f() stack

    frame ポインタ渡し {f() s.f. addr} {num addr} {main() s.f. addr} {num addr} 10 {num addr}
  46. . . . {f() s.f. addr} {num addr} {main() s.f.

    addr} {num addr} main() stack frame • 3行目で呼び出し 10 f() stack frame ポインタ渡し {num addr} main()関数の numに 影響を及ぼす
  47. 古のヒープ管理 . . . . . . . {main() s.f.

    addr} main() stack frame C言語を例にとると • func malloc(int size) pointer • func free(pointer memory) (上記関数は疑似コード)
  48. 古のヒープ管理 C言語を例にとると • func malloc(int size) pointer • func free(pointer

    memory) (上記関数は疑似コード) . . . . . . . ~~~ {ヒープのどこか} {main() s.f. addr} main() stack frame {ヒープのどこか}
  49. 古のヒープ管理 . . . . . . . {main() s.f.

    addr} main() stack frame {ヒープのどこか} C言語を例にとると • func malloc(int size) pointer • func free(pointer memory) (上記関数は疑似コード)
  50. ヒープ領域 スタック領域 マーク & スイープ main() stack frame a() stack

    frame b() stack frame a b c e {a addr} d {b addr} {var1 addr} var1 {d addr}
  51. ヒープ領域 スタック領域 マーク段階 main() stack frame a() stack frame b()

    stack frame a b c e {a addr} d {b addr} {var1 addr} var1 {d addr}
  52. ヒープ領域 スタック領域 マーク段階 main() stack frame a() stack frame b()

    stack frame a b c e {a addr} d {b addr} {var1 addr} var1 {d addr}
  53. ヒープ領域 スタック領域 マーク段階 main() stack frame a() stack frame b()

    stack frame a b c e {a addr} d {b addr} {var1 addr} var1 {d addr}
  54. ヒープ領域 スタック領域 マーク段階 main() stack frame a() stack frame b()

    stack frame a b c e {a addr} d {b addr} {var1 addr} var1 {d addr}
  55. ヒープ領域 スタック領域 マーク段階 main() stack frame a() stack frame b()

    stack frame a b c e {a addr} d {b addr} {var1 addr} var1 {d addr}
  56. ヒープ領域 スタック領域 マーク段階 main() stack frame a() stack frame b()

    stack frame a b c e {a addr} d {b addr} {var1 addr} var1 {d addr}
  57. ヒープ領域 スタック領域 マーク段階 main() stack frame a() stack frame b()

    stack frame a b c e {a addr} d {b addr} {var1 addr} var1 {d addr}
  58. ヒープ領域 スタック領域 main() stack frame a() stack frame b() stack

    frame a b e {a addr} d {b addr} {var1 addr} var1 {d addr} スイープ段階 c
  59. ヒープ領域 スタック領域 main() stack frame a() stack frame b() stack

    frame a b e {a addr} d {b addr} {var1 addr} var1 {d addr} スイープ段階
  60. … … ヒープ内でのオブジェクト管理 • 同サイズのオブジェクトをスパンでまとめる int (8 byte) struct (8

    byte) struct struct (32 byte) struct struct int int スパン1 (8 byte用) スパン2 (8 byte用) スパン3 (32 byte用) int int
  61. 【つらみ】ヒープも爆発する • スタック領域を大きくするといいのでは…? • スタックが大きい = ヒープが小さい ◦ ✅ スタックオーバーフローは

    起こりにくくなる ◦ ❌ OOM killの確率が上がる😭 • あちらを立てればこちらが立たず状態 . . . . . Heap var main() stack frame 󰷺
  62. ざっくりしたイメージ main goroutine main() stack frame a() stack frame a2()

    stack frame a3() stack frame a4() stack frame m2() stack frame b() stack frame a goroutine b goroutine
  63. スタックコピー . . . main() stack frame m2() stack frame

    m3() stack frame m3()を実行する ためには サイズが足りない
  64. スタックコピー . . . main() stack frame m2() stack frame

    2倍のサイズ 新しい main goroutine のスタック
  65. スタックコピー . . . main() stack frame m2() stack frame

    m3() stack frame これでm3()が 実行できる