メモリリークとは?

メモリリークとは、プログラムで不要になったデータやオブジェクトが、メモリ上に残り続けてしまう状態のことです。
JavaScriptにはガベージコレクションという仕組みがあり、不要になったメモリは自動的に解放されます。しかし、どこかから参照され続けているデータは「まだ必要」と判断されるため、メモリに残り続けることがあります。
メモリリークが増えると、ページの動作が重くなったり、長時間利用するWebアプリでパフォーマンスが低下したりする原因になります。
メモリリークが発生するケース
JavaScriptでメモリリークが発生しやすい代表的なケースには、次のようなものがあります。
- グローバル変数の過剰な使用: グローバルスコープにデータを保持し続けると、不要になっても解放されにくくなります。
- 未削除のイベントリスナー: 不要になった要素や処理にイベントリスナーが残ると、参照が残り続けることがあります。
- クロージャによる参照の保持: クロージャが大きなデータを参照し続けると、メモリが解放されない場合があります。
- 不要なDOM要素への参照: DOMから削除した要素をJavaScript側の変数が参照し続けると、解放されにくくなります。
- タイマーの解除忘れ:
setInterval()などを止めずに残すと、処理や参照が残り続けることがあります。
メモリリークの防止策
メモリリークを防ぐには、不要になった参照を残さないことが重要です。代表的な防止策を見ていきましょう。
グローバル変数の適切な管理
グローバル変数はどこからでも参照できるため便利ですが、必要以上に使うとメモリ上にデータが残りやすくなります。
// メモリに残りやすい例
const globalData = [];
function addData(item) {
globalData.push(item);
}
// 必要な範囲だけでデータを使う例
function addData(item) {
const localData = [];
localData.push(item);
return localData;
}
この例では、必要なデータを関数内で扱うようにしています。
すべてのグローバル変数が悪いわけではありませんが、長期間保持する必要がないデータは、できるだけスコープを限定しましょう。
不要なイベントリスナーの削除
イベントリスナーが不要になった場合は、removeEventListener()で削除できます。
const button = document.getElementById("myButton");
function handleClick() {
console.log("クリックされました");
}
button.addEventListener("click", handleClick);
// 不要になったらイベントリスナーを削除
button.removeEventListener("click", handleClick);
button.remove();
イベントリスナーを削除するには、追加時と同じ関数を指定する必要があります。
そのため、匿名関数を直接渡すと削除しにくくなる場合があります。
AbortControllerを使ったイベントリスナーの管理
現在のJavaScriptでは、AbortControllerを使ってイベントリスナーをまとめて解除する方法も利用できます。
const controller = new AbortController();
const button = document.getElementById("myButton");
button.addEventListener("click", () => {
console.log("クリックされました");
}, {
signal: controller.signal
});
// 不要になったらまとめて解除
controller.abort();
複数のイベントリスナーをまとめて管理したい場合に便利です。
クロージャの参照に注意する
クロージャは外側の変数を保持できる便利な仕組みですが、大きなデータを参照し続けるとメモリリークの原因になることがあります。
function createHandler() {
const largeArray = new Array(100000).fill("data");
return function () {
console.log(largeArray[0]);
};
}
const handler = createHandler();
handler();
この例では、返された関数がlargeArrayを参照し続けます。
クロージャ自体は問題ではありませんが、不要になったデータを長く保持しないように設計することが重要です。
不要なDOM要素への参照を残さない
DOM要素を画面から削除しても、JavaScriptの変数がその要素を参照し続けていると、メモリに残ることがあります。
let element = document.createElement("div");
document.body.appendChild(element);
// DOMから削除
element.remove();
// 参照も不要なら解除
element = null;
通常はDOMから削除すれば十分な場合もありますが、大きな要素や長時間動作するアプリでは、不要な参照を残さないように注意しましょう。
タイマーを解除する
setInterval()やsetTimeout()も、不要になったら解除することが大切です。
const timerId = setInterval(() => {
console.log("定期的に実行");
}, 1000);
// 不要になったら停止
clearInterval(timerId);
特にSPAや長時間開いたまま使う画面では、タイマーの解除忘れがメモリリークや不要な処理の原因になります。
メモリリーク防止のチェックリスト
| 防止策 | 具体例 |
|---|---|
| グローバル変数を増やしすぎない | 必要なデータはできるだけローカルスコープで扱う。 |
| イベントリスナーを解除する | 不要になったらremoveEventListener()やAbortControllerを使う。 |
| クロージャの参照に注意する | 大きなデータを不要に保持し続けない。 |
| DOM要素への参照を残さない | 削除したDOM要素を変数で持ち続けない。 |
| タイマーを停止する | setInterval()は不要になったらclearInterval()で解除する。 |
初心者が覚えておきたいポイント
- JavaScriptにはガベージコレクションがあるため、通常は手動でメモリ解放する必要はありません。
- ただし、参照が残っているデータは不要になっても解放されません。
- イベントリスナー、タイマー、DOM参照はメモリリークの原因になりやすいです。
- SPAのように長時間動作するアプリでは、特にメモリ管理が重要です。
- Chrome DevToolsなどの開発者ツールを使うと、メモリ使用量を確認できます。
まとめ
- メモリリークとは、不要になったデータがメモリに残り続ける状態である。
- JavaScriptにはガベージコレクションがあるが、参照が残っているデータは解放されない。
- グローバル変数、イベントリスナー、クロージャ、DOM参照、タイマーはメモリリークの原因になりやすい。
- 不要なイベントリスナーやタイマーは適切に解除することが重要である。
- DOM要素を削除した後も、JavaScript側の参照が残っていないか注意する。
- 長時間動作するWebアプリでは、メモリリーク対策がパフォーマンス維持につながる。