テック技報最新号へ →
コード

Go: 大きなスライスの一部を保持すると元の配列ごと残る(slices.Clone で切り離す)

hdr := buf[:64] は 64 バイトのコピーではなく、10MB の buf と同じ下の配列を指す。hdr をキャッシュなどに長く持つと、GC は 10MB 全体を解放できない。長く使う部分はコピーしてから保持する。

@amitsinha__07 · 投稿 2026-10-17
#go#memory#gc#slice

どんな時に起きる?

  • 大きなファイルやレスポンスを []byte に読み、ヘッダーや ID だけ切り出して map やキャッシュに入れる
  • 巨大な文字列から s[i:j] で部分文字列を取り出して保持する(string も同じ仕組み)
  • ヒーププロファイルで、読み込み関数の割り当てが解放されずに残り続ける

書き方

go
data, _ := os.ReadFile(path) // 10MB

// NG: data 全体が cache から参照され続ける
cache[key] = data[:64]

// OK: 必要な分だけ新しい配列にコピーする
cache[key] = bytes.Clone(data[:64])   // Go 1.20+
// または slices.Clone(data[:64])     // Go 1.21+、任意の要素型

// 文字列の場合
name := strings.Clone(line[10:20])    // Go 1.20+

1.20 より古い Go なら append([]byte(nil), data[:64]...) で同じことができる。

注意点

  • 3 要素スライス式 data[:64:64] は容量を制限するだけで、下の配列は同じ。メモリは解放されない。
  • コピーは長く保持する部分だけでよい。関数内ですぐ使い終わるなら切り出しのままで問題ない(コピーのほうが無駄)。
  • ポインタを含む要素型([]*T など)では、切り出した範囲の外の要素が指すオブジェクトも残る。不要な要素は nil にしておく。

出典

X の元の投稿を開く