デバッグツール比較&活用

なぜかWebページが重くなる?DevTools Memoryタブで探るメモリリークの探し方

Tags: メモリリーク, デバッグ, JavaScript, Chrome DevTools, Memoryタブ, パフォーマンス

Webサイトを開発していると、「なぜかページが徐々に重くなる」「長時間開いているとブラウザのメモリ使用量が異常に増える」といった現象に遭遇することがあります。その原因の一つに、メモリリークが挙げられます。

メモリリークは、不要になったメモリ領域が解放されずに残り続けてしまう状態を指します。特にJavaScriptのようなガベージコレクション(GC)を持つ言語では、本来GCが不要になったオブジェクトのメモリを自動的に解放してくれるはずですが、意図しない参照が残ってしまうことでリークが発生します。

この記事では、Web開発のジュニア開発者の皆様が直面しやすい、クライアントサイド(ブラウザ)でのメモリリークに焦点を当て、ブラウザの開発者ツール(DevTools)に含まれるMemoryタブを使って、どのようにメモリリークを発見し、その原因を特定するのかをステップバイステップで解説します。

メモリリークが引き起こす問題

メモリリークが発生すると、以下のような問題がユーザーやアプリケーションにもたらされる可能性があります。

ブラウザDevToolsのMemoryタブとは

主要なモダンブラウザ(Chrome, Firefox, Edgeなど)の開発者ツールには、Memoryタブが搭載されています。このタブでは、Webページが使用しているメモリの状況を詳細に分析することができます。

Memoryタブで主に利用される機能には、以下のようなものがあります。

メモリリークの診断では、主にHeap snapshot機能が強力な武器となります。

Memoryタブを使ったメモリリーク診断の基本手順

メモリリークの診断は、以下の基本的なステップで進めるのが一般的です。

  1. 疑わしい操作の特定: メモリリークが疑われる状況(例: 特定のページの表示、モーダルウィンドウの開閉、Ajaxリクエストの繰り返しなど)を特定します。
  2. ベースラインのスナップショット取得: 何も操作を行っていない、クリーンな状態(または操作開始前)でHeap snapshotを取得します。
  3. 疑わしい操作の実行: メモリリークが疑われる操作を数回(2~3回程度)繰り返します。操作ごとにページを離れるなど、本来メモリが解放されるべきシナリオを再現します。
  4. 比較用のスナップショット取得: 操作を繰り返した後、Heap snapshotを再度取得します。
  5. スナップショットの比較と分析: 取得した複数のスナップショットを比較し、不要なオブジェクトが増加し続けていないかを確認します。

この手順を、Chrome DevToolsのMemoryタブを例に具体的に見ていきましょう。

Step 1: DevToolsを開きMemoryタブを選択

デバッグしたいページを開き、F12キーまたは右クリックメニューから「検証」(または「開発者ツール」)を選択してDevToolsを開きます。「Memory」タブをクリックして選択します。もしMemoryタブが表示されていない場合は、タブの右側にある「その他のツール」アイコン(三点リーダーなど)から選択してください。

Step 2: ベースラインのHeap snapshotを取得

Memoryタブを開いたら、プロファイルタイプの選択肢から「Heap snapshot」が選択されていることを確認します。

Heap snapshotを選択 (注:上記は表示イメージです。実際のUIとは異なる場合があります。)

準備ができたら、左上の丸い記録ボタン、または画面下部にある「Take snapshot」ボタンをクリックします。

Take snapshotボタン (注:上記は表示イメージです。実際のUIとは異なる場合があります。)

スナップショットの取得には少し時間がかかる場合があります。完了すると、左側のパネルにスナップショットが一覧表示されます(例: Snapshot 1)。これがベースラインとなります。

Step 3: 疑わしい操作を複数回実行

メモリリークが疑われる操作を数回実行します。例えば、特定のデータを表示するモーダルを開いて閉じる、Ajaxリクエストを繰り返し実行するなど、メモリが解放されるはずの操作を意識的に行います。この時、操作の間にGCを強制的に実行させるために、DevToolsのPerformanceタブなどでゴミ箱アイコンをクリックする方法もありますが、まずは自然な操作で確認を進めます。

Step 4: 比較用のHeap snapshotを取得

操作を数回繰り返した後、再度「Take snapshot」ボタンをクリックして、新しいスナップショットを取得します(例: Snapshot 2, Snapshot 3)。これにより、操作によってメモリ使用量がどのように変化したかを比較するためのデータが揃います。

Step 5: スナップショットの比較と分析

取得した複数のスナップショットを比較します。例えば、Snapshot 2を選択した状態で、サマリービューの上部にある比較対象のドロップダウンメニューからSnapshot 1を選択します。

Snapshot比較ドロップダウン (注:上記は表示イメージです。実際のUIとは異なる場合があります。)

比較ビューに切り替わると、各オブジェクトの項目に「#Delta」「Size Delta」などの列が表示されます。「#Delta」はオブジェクトインスタンス数の増減、「Size Delta」はメモリ使用量の増減を示します。

メモリリークの兆候として最も重要なのは、本来インスタンス数が操作後にゼロになるべきオブジェクト(例: 閉じられたモーダルに関連するDOMノードやJavaScriptオブジェクト)や、繰り返し操作によってインスタンスが増加し続けるオブジェクトの「#Delta」や「Size Delta」が正の値になっていることです。

例えば、モーダルを開閉する操作を繰り返した場合、モーダルに関連するDOMノード(HTMLDivElementなど)や、モーダル表示を制御するJavaScriptオブジェクトのインスタンス数が、開閉操作を繰り返すたびに増加し続ける(Snapshot 3 vs Snapshot 2 で正のDeltaが見られる)のであれば、そのオブジェクトがリークしている可能性が非常に高いです。

疑わしいオブジェクトを見つけたら、その項目をクリックして詳細を確認します。詳細ビューでは、そのオブジェクトを「Retainers」リストを見ることができます。Retainersは、そのオブジェクトがメモリ上に保持されている理由、すなわちそのオブジェクトを参照している他のオブジェクトやクロージャなどを示します。このRetainersパスをたどっていくことで、どのオブジェクトからの参照がリークを引き起こしているのか、原因となっているコード箇所を特定する手がかりを得られます。

Retainersビュー (注:上記は表示イメージです。実際のUIとは異なる場合があります。)

メモリリークのよくある原因と対策

Heap snapshotの分析で見つかる可能性のある、ジュニア開発者が遭遇しやすいメモリリークの原因と、その対策の例をいくつか挙げます。

まとめ

メモリリークはWebアプリケーションのパフォーマンスに深刻な影響を与える可能性がありますが、ブラウザの開発者ツール、特にMemoryタブのHeap snapshot機能を活用することで、効果的に診断し、原因を特定することが可能です。

この記事で解説したHeap snapshotの取得と比較、Retainersによる参照パスの追跡といった基本的な手順は、メモリリーク解決に向けた強力な手がかりとなります。ぜひ、ご自身の開発プロジェクトで「なぜか重い」と感じる場面があれば、Memoryタブを開いて、メモリの状況を観察してみてください。

原因の特定には、JavaScriptの参照の仕組みやガベージコレクションに関する理解も役立ちます。地道な分析が必要となる場合もありますが、ツールを正しく使いこなすことで、難解なメモリ関連の問題も一つずつ解決していくことができるでしょう。効率的なデバッグスキルを身につけ、より安定した高品質なWebアプリケーション開発を目指しましょう。