TL

WeakMap・WeakRef・FinalizationRegistryと弱参照の意味論

キャッシュやメタデータの保持でメモリを漏らさない方法がわかる。弱参照がGCの到達可能性をどう変えるか、WeakMapがキーをリークさせない原理、ファイナライザの非決定性と落とし穴を内部動作から解説します。

応用JavaScriptメモリ管理ガベージコレクションWeakMapブラウザ最終更新: 2026-06-21
TL;DR要点だけ先に
  • 1.弱参照はGCの到達可能性に寄与しない。対象が弱参照からしか辿れなくなれば、GCはそのまま回収できる。
  • 2.WeakMapはキーを弱く持つため、キーが他から到達不能になるとエントリごと自動で消える。だから列挙不可・サイズ取得不可。
  • 3.WeakRef.derefとFinalizationRegistryのコールバックは実行タイミングが非決定的で、呼ばれない可能性もある。クリーンアップの唯一の手段にしてはいけない。

なぜ弱参照を学ぶのか

JavaScriptのGCは「もう使わないオブジェクト」ではなく、どこからも辿れなくなった到達不能なオブジェクトだけを回収します。詳しくは ガベージコレクションのブラウザ内動作 で扱いましたが、ここに実務上のジレンマがあります。「あるオブジェクトに付随情報を結び付けたい。ただしそのオブジェクトの寿命までは延ばしたくない」というケースです。通常の参照(強参照)でMapに入れればキーは決して消えず、リークします。これを解くのが弱参照であり、WeakMapWeakSetWeakRefFinalizationRegistryの4つです。本稿はこれらの意味論を、GCの到達可能性という一点から正確に解きほぐします。前提となる値とオブジェクトの扱いは JavaScript を参照してください。

強参照と弱参照:到達可能性への寄与の差

参照には2種類あります。**強参照(strong reference)**は、そこから辿れる対象を生存扱いにします。GCルート(グローバル、実行スタック上の変数など)から強参照だけでオブジェクトに辿り着けるなら、それは到達可能=回収されません。

弱参照(weak reference)は、対象を指してはいるが到達可能性の判定に寄与しない参照です。あるオブジェクトへの参照が弱参照しか残っていない状態を「弱到達可能(weakly reachable)」と呼びます。GCはこのオブジェクトを「実質ゴミ」とみなし、いつでも回収できます。

弱参照は“見えるが掴まない”

強参照は対象を掴んで離さない手。弱参照は対象を指す矢印にすぎません。GCがマーキングで生存を辿るとき、強参照の鎖はたどりますが、弱参照は無視します。だから「強参照で1本でも辿れれば生存」「弱参照しか残っていなければ回収可」という非対称が生まれます。

WeakMap:キーを弱く持つ連想配列

WeakMapは、キーへの参照を弱参照で保持するMap類似のコレクションです。Mapとの差は到達可能性のモデルにあります。

観点MapWeakMap
キーの参照種別強参照(キーを生かし続ける)弱参照(キーを生かさない)
キーに使える型任意の値(プリミティブ可)オブジェクトと未登録Symbol限定
列挙・size可能(keys/values/sizeあり)不可(iterableでない)
エントリの消滅明示deleteまで残るキーが他から到達不能になると自動消滅

通常のMapに要素をキーとして入れると、Mapがキーを強参照するため、その要素はDOMから外してもMap経由で到達可能なまま残りリークします。WeakMapではキーが弱く保持されるので、キーが他のどこからも強参照されなくなった瞬間、そのキーは弱到達可能になり、GCがキーごとエントリ(値も含む)を回収します。手動のdeleteなしに掃除が進むのが本質です。

const meta = new WeakMap();
function attach(el) {
  // el を生かさずに付随情報を結び付ける
  meta.set(el, { createdAt: Date.now() });
}
// el が他から参照されなくなれば、meta 内のエントリも自動で消える

なぜ列挙できないのか

WeakMapkeys()sizeが無いのは設計上の制約ではなく、意味論的に成立しないからです。エントリが生きているかはGCのタイミングに依存します。もし列挙できれば、「いつ消えるか」がGCの実行タイミングという観測不能・非決定的な事象を通じてプログラムから見えてしまい、GCの挙動が言語仕様の観測可能な振る舞いに漏れ出します。列挙手段を持たないことで、「弱到達可能になったキーはユーザコードから二度と取り出せない」を保証し、回収タイミングを完全にエンジン任せにできます。WeakSetも同じ理屈で、値を弱く持ち列挙できません。

値はキーを介して間接的に強参照される

WeakMapが弱いのはキーへの参照だけです。値は通常の強参照で保持されます。だから値の中からそのキー自身を強参照すると(あるいは値がキーを閉じ込めると)、キーが永遠に到達可能になりエントリは消えません。値からキーへ戻る参照を作らないのが鉄則です。エンジンはこの循環を「エフェメロン(ephemeron)」というGCアルゴリズムで正しく扱い、キーが他から到達不能なら値もろとも回収します。

WeakRef:個別オブジェクトへの弱い参照

WeakMapがコレクションなのに対し、WeakRef単一オブジェクトへの弱参照を明示的に作るプリミティブです。new WeakRef(obj)で生成し、ref.deref()で対象を取り出します。

const ref = new WeakRef(bigObject);
// 後で取り出す
const obj = ref.deref();
if (obj !== undefined) {
  use(obj);          // まだ生存していた
} else {
  // 既に回収済み。再取得や再生成が必要
}

deref()は、対象がまだ生存していればそのオブジェクトを、既に回収されていればundefinedを返します。注意すべきは到達可能性への影響です。deref()オブジェクトを返した瞬間、その戻り値は強参照になります。つまり、現在のマイクロタスク(より正確には現在の同期実行の最後まで)の間はそのオブジェクトの回収が妨げられます。これは「derefで取れたのに次の行で消えていた」という不整合を防ぐための仕様で、同一ターン内ではderefの結果が一貫することが保証されます。

FinalizationRegistry:回収を“通知”する

FinalizationRegistryは、登録したオブジェクトが回収された後にコールバック(ファイナライザ)を呼んでもらう仕組みです。回収そのものではなく、回収の事後通知を受け取り、関連する外部リソース(キャッシュエントリ、開いたハンドルなど)を片付ける用途を想定します。

const registry = new FinalizationRegistry((heldValue) => {
  // target が回収された後に呼ばれ得る。heldValue で対象を識別
  cleanup(heldValue);
});
registry.register(target, "target-id-123");

registerの第1引数が監視対象、第2引数(held value)はコールバックに渡される識別子です。ここで決定的に重要なのは、held valueに対象自身を渡してはいけないことです。コールバックに渡すために対象を強参照すると、対象が永遠に到達可能になり、そもそも回収されずコールバックも呼ばれません。

ファイナライザの非決定性:最大の落とし穴

WeakRefFinalizationRegistryは強力ですが、その実行タイミングは非決定的で、仕様上ほとんど何も保証されません。これを理解せずに使うと壊れます。

保証されないこと内容
呼ばれるタイミングGCがいつ走るかは未規定。回収から通知まで任意に遅延し得る
そもそも呼ばれるかプログラム終了時など、ファイナライザが一度も呼ばれない場合がある
呼ばれる順序複数登録しても回収順・通知順は保証されない
実行スレッド/文脈通知は別タスクとして非同期に届く。同期的な後始末には使えない

非決定性の根は単純で、これらの観測がすべてGCの内部都合に依存する点にあります。エンジンはインクリメンタルや並行回収(JavaScriptエンジンの内部 で触れたV8など)でGCを分割実行するため、「いつ回収したか」は外から予測できません。仕様もあえて緩く定め、実装が最適なGC戦略を選べる余地を残しています。

ファイナライザを正規の解放手段にしない

DBコネクション・ファイルハンドル・ロックなど、確実に解放すべきリソースをファイナライザだけに頼ってはいけません。呼ばれない・遅れるが許容されるからです。確実な解放には明示的なclose()/dispose()やtry-finally、あるいはusing宣言(明示的リソース管理)を使い、FinalizationRegistryは「閉じ忘れを最後に拾う保険」「外部キャッシュの掃除のヒント」程度に留めます。

使い分けの指針

やりたいこと選ぶもの理由
オブジェクトにメタ情報を結び付けるWeakMapキーの寿命を延ばさず、消えれば自動で掃除
オブジェクトの集合への所属判定WeakSet要素を生かさずにメンバーシップだけ持つ
キャッシュ等で対象を弱く掴むWeakRef生きていれば再利用、消えていれば再生成
回収後に後始末のヒントが欲しいFinalizationRegistry事後通知。ただし保証なし・保険用途
確実なリソース解放明示close / usingGC非依存で決定的に解放できる

実務では迷ったらまずWeakMapです。多くの「対象を生かさずに付随データを持ちたい」要求はこれで解決し、列挙不能という制約も「掃除を完全自動化する代償」として理にかなっています。WeakRefFinalizationRegistryは、画像やパース結果の弱いキャッシュ、外部リソースの漏れ検出など、非決定性を許容できる限られた場面でのみ使います。

まとめ

まとめ

弱参照は到達可能性に寄与しない参照で、対象が弱参照からしか辿れなくなれば(弱到達可能)GCはそのまま回収できます。WeakMap/WeakSetはキー/値を弱く持つため、対象が他から到達不能になるとエントリごと自動消滅し、ゆえに列挙不可・sizeなし——これはGCの非決定性を観測可能な振る舞いに漏らさないための必然です。WeakRef.deref()は生存していれば対象を返し、その瞬間に同一ターン内は強参照化されます。FinalizationRegistryは回収の事後通知ですが、呼ばれる時刻・有無・順序のいずれも保証されません。だからファイナライザは確実な解放手段にせず、明示的なclose()/usingを主とし、弱参照系は「寿命を延ばさず結び付ける」「漏れを最後に拾う保険」として使うのが正解です。

Web/フロントエンド Article

WeakMap・WeakRef・FinalizationRegistryと弱参照の意味論を実務で読む

TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。

解決すること

JavaScript

比較で見る軸

難易度: advanced / カテゴリ: Web/フロントエンド / タグ数: 5

導入後に効く点

WeakMapはキーを弱く持つため、キーが他から到達不能になるとエントリごと自動で消える。だから列挙不可・サイズ取得不可。

先に潰すリスク

用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。

数字・仕様の読み方
難易度
advanced
カテゴリ
Web/フロントエンド
タグ数
5

判断チェックリスト

  • 自社の用途が「JavaScript / メモリ管理」に近いか確認する。
  • 強みである「弱参照はGCの到達可能性に寄与しない。対象が弱参照からしか辿れなくなれば、GCはそのまま回収できる。」が本当に評価軸になるか確認する。
  • 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
  • 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
  • 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
  • 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。

次に確認する観点

JavaScriptメモリ管理ガベージコレクションWeakMapブラウザJavaScriptメモリ管理ガベージコレクション
参考: 公式情報