メモリ管理

プログラムがメモリを割り当て・解放する仕組み

メモリ管理方式

方式仕組み言語例特徴
手動管理malloc/freeC, C++高速、制御可能
GC自動回収Go, Java, Python安全、簡単
所有権コンパイル時解決RustGCなしで安全
ARC参照カウントSwift, ObjC予測可能

スタック vs ヒープ

スタック

  • • 関数呼び出しで自動割り当て
  • • LIFO(後入れ先出し)
  • • 高速なアクセス
  • • サイズが固定(コンパイル時に決定)
  • • ローカル変数、関数引数

ヒープ

  • • 明示的に割り当て/解放
  • • 任意の順序でアクセス
  • • スタックより遅い
  • • 動的サイズ
  • • 動的配列、オブジェクト
1// Go - エスケープ解析
2func stack() int {
3 x := 42 // スタックに割り当て
4 return x // 値コピーで返す
5}
6
7func heap() *int {
8 x := 42 // ヒープに割り当て(エスケープ)
9 return &x // ポインタを返すので関数終了後も必要
10}
11
12// go build -gcflags="-m" で確認可能

ガベージコレクション(GC)

使われなくなったメモリを自動的に検出し、解放する仕組み。

マーク&スイープ

1. ルートから到達可能なオブジェクトをマーク
2. マークされていないオブジェクトを解放

世代別GC

オブジェクトを世代で分類。若い世代を頻繁に、古い世代をまれに回収。 ほとんどのオブジェクトは短命という仮説に基づく。

Go のGC

並行マーク&スイープ。低レイテンシを重視(STW時間を最小化)。 GOGC環境変数でチューニング可能。

Rustの所有権システム

コンパイル時にメモリ安全性を保証。GCなしでメモリリークやダングリングポインタを防ぐ。

1. 各値には所有者が1つだけ
2. 所有者がスコープを外れると値は破棄
3. 所有権は移動(move)または借用(borrow)できる
1// Rust - 所有権
2fn main() {
3 let s1 = String::from("hello"); // s1が所有
4 let s2 = s1; // 所有権がs2に移動
5 // println!("{}", s1); // エラー!s1は無効
6
7 let s3 = String::from("world");
8 let len = calculate_length(&s3); // 借用(参照を渡す)
9 println!("{} has {} chars", s3, len); // s3はまだ有効
10}
11
12fn calculate_length(s: &String) -> usize {
13 s.len()
14} // sはスコープを外れるが、所有していないので何も起きない

参照カウント(ARC)

オブジェクトへの参照数をカウント。ゼロになったら解放。 循環参照に注意(弱参照で対処)。

1// Rust - Rc (Reference Counted)
2use std::rc::Rc;
3
4fn main() {
5 let a = Rc::new(vec![1, 2, 3]);
6 println!("count: {}", Rc::strong_count(&a)); // 1
7
8 let b = Rc::clone(&a); // 参照カウント増加
9 println!("count: {}", Rc::strong_count(&a)); // 2
10
11 {
12 let c = Rc::clone(&a);
13 println!("count: {}", Rc::strong_count(&a)); // 3
14 } // cがスコープ外 → カウント減少
15
16 println!("count: {}", Rc::strong_count(&a)); // 2
17}

メモリ関連の問題

メモリリーク

使わないメモリが解放されない。GC言語でも循環参照で発生可能。

ダングリングポインタ

解放済みメモリを参照。Rustは所有権で防止。

バッファオーバーフロー

境界を超えたメモリアクセス。Go/Rustは境界チェック。

データ競合

複数スレッドからの同時アクセス。Rustは借用チェッカーで防止。

Goのメモリ最適化

1// sync.Pool - オブジェクトの再利用
2var bufferPool = sync.Pool{
3 New: func() interface{} {
4 return new(bytes.Buffer)
5 },
6}
7
8func process() {
9 buf := bufferPool.Get().(*bytes.Buffer)
10 defer func() {
11 buf.Reset()
12 bufferPool.Put(buf)
13 }()
14 // bufを使用
15}
16
17// スライスの事前アロケーション
18slice := make([]int, 0, 1000) // 容量1000を事前確保
19for i := 0; i < 1000; i++ {
20 slice = append(slice, i) // 再アロケーションなし
21}