最終更新日: 2018年5月14日.
v3.x のドキュメントを見たい場合はこちら, もしあなたが Vue でアプリケーションを構築しているとき、メモリリークに注意する必要があります。 この Issue はシングルページアプリケーション( SPA )を設計する際には特に重要で、SPA を使っているときユーザはブラウザをリフレッシュする必要はなく、コンポーネントをクリーンアップすることとガベージコレクションが期待通りに動作することを確認することは JavaScript アプリケーションの責務です。, Vue アプリケーションのメモリリークは一般的に Vue 自体からは起こらず、むしろ他のライブラリをアプリケーションに組み込む際発生する可能性があります。, 次の例のメモリリークは Vue コンポーネント上で Choices.js ライブラリが原因で引き起こされていて正しく解放されません。それでは、どのようにして Choices.js のフットプリントを取り除くかメモリリークを回避するかを示します。, 以下の例では、たくさんのオプションを持つ select をロードしてから v-if ディレクティブを使用して show/hide ボタンを使って追加したり仮想 DOM から削除しています。この例での問題は v-if ディレクティブが DOM から親要素を取り除きますが、 Choices.js で生成された追加 DOM 部分をクリーンアップできずに、メモリリークを引き起こします。, このメモリリークを確認するためには、この CodePen の例 を Chrome で開き、そして Chrome タスクマネージャー を開きます。Chrome タスクマネージャーを Mac で開くためには、Chrome トップナビゲーション > ウィンドウ > タスクマネージャーを選択するか、 Windows の場合 Shift + ESC ショートカットを使用します。そして、 show/hide ボタンを50回またはそれ以上クリックしてください。Chrome タスクマネージャー上でメモリ使用量が増え、再利用されないことが確認できます。, 上記の例では、DOM から select を削除する前に、hide() メソッドを使っていくつかのクリーンアップとメモリリークの解決ができます。これを実現するためには、 Vue インスタンスのデータオブジェクトにプロパティを保持し、 Choices API の destroy() メソッドを使用してクリーンアップを実行します。, メモリ管理とパフォーマンステストはアプリケーションを素早くリリースする興奮のためかんたんに無視できますが、小さなメモリフットプリントを維持することは全体のユーザーエクスペリエンスにとって依然として重要です。, ユーザーが使用している可能性があるデバイスの種類と通常のフローを検討します。ユーザーはメモリが制約を受けたラップトップまたはモバイルデバイスを使用していますか?ユーザーは通常アプリケーション内をたくさん回遊していますか?これらのいずれかが当てはまる場合、良いメモリ管理方法はユーザーのブラウザをクラッシュさせる最悪のシナリオを回避させるのに役立つはずです。これらのいずれかにも当てはまらない場合でも、注意を払わなければあなたのアプリケーションの長時間の使用でパフォーマンスが低下する可能性があります。, 上記の例では、v-if ディレクティブを使用してメモリリークを説明しましたが、シングルページアプリケーションのコンポーネントに vue-router を使用してルーティングする場合、より一般的で現実的なシナリオが発生します。, v-if ディレクティブのように、vue-router は仮想 DOM から要素を削除しユーザーがアプリケーションを回遊するときにそれらを新しい要素で置き換えます。Vue beforeDestroy() ライフサイクルフックは vue-router ベースで構築されているアプリケーションの同じ種類の課題を解決することに適しています。.  Vueコンポーネントを指すクリックリスナーがあります。コンポーネント自体が破壊されたため、GCすることはできません。, 代わりに   $emit('close') さらに、VueJSでメモリリークを引き起こす可能性のある他の潜在的な問題はありますか? Chrome devtoolsの[メモリ]タブを使用する以外に、メモリリークをどのようにデバッグする必要がありますか? を使用してみました https://codepen.io/tomshort5/pen/BaBLXvb, これは、特定のアクションの後にメモリのスナップショットを作成することで最もわかりやすくなります。ページがロードされると、単一のVueインスタンスが作成されます。, イベントを介して子が削除されるようにトリガーした後、VueComponentはメモリから削除されません。もう一度「トグル」をクリックすることと比較すると、コンポーネントがメモリから削除されていることがわかります。, 最後にクリックされたDOMノードへの参照を保持しているようです。したがって、 返信 | 引用 回答 text/html 2015/07/06 14:42:33 佐祐理 2. v-if この記事は sessionstack blog に投稿されている、How JavaScript works シリーズの一記事 "How JavaScript works: memory management + how to handle 4 common memory leaks" の和訳です。投稿されたのは Alexander Zlatkov, 原文はこちらです。翻訳については許諾いただいています。, メモリ管理もしくはC言語におけるメモリ解説他、用語なども怪しい箇所は多分にありますので、間違いがありましたら修正のご指摘・編集リクエスト等ください。, 数週間前、私たちは JavaScript を深く掘り下げ、実際にどのように動作するかを目的とした連載を開始しました。JavaScript が構成する要素を知り、どう付き合っていくかを知ることで、より良いコードやアプリケーションを書くことができると考えました。, シリーズの最初の記事は、 エンジン、ランタイム、コールスタックの概要を提供することに重点を置いていました。 2番目の記事では、Google の V8 JavaScript エンジンの内部を詳しく調べ、優れた JavaScript コードの書き方に関するヒントを紹介しました。, この3回目の記事では、多くの開発者が普段扱うプログラム言語への習熟度を上げようとして無視しがちであり、クリティカルな問題でもあるメモリ管理について話していきます。また SessionStack がメモリリークを起こさないようにするため、もしくは我々が統合している Web アプリケーションのメモリ消費を増加させないため、JavaScript でメモリリークを処理する方法について、いくつかのヒントを紹介していきます。, C のような言語には低レベルのメモリ管理がプリミティブで備わっています、malloc(), free() のようなものです。これらのプリミティブな関数はメモリ解放を行い OS に対してメモリを割り当てるなど開発者によって明示的に使用されます。, 同じように、JavaScript もオブジェクトや文字列が作成された際にメモリ割当を行い、それらがもう使用されないと判断された時に、"自動的に" メモリ開放を行います。これがいわゆるガベージコレクションというプロセスです。これは一見すると "自動的に" リソースの開放を行うように思えてしまい、ある種の混乱を招いてしまう原因なのです。さらにこれによって JavaScript(または他の高水準言語)を扱う開発者が、メモリ管理に関して気をかけることに注力しなくてよいという誤解を生んでしまいます。これが大きな間違いなのです。, 高水準言語を扱って仕事をする時も、開発者はメモリ管理について(もしくは少なくとも基礎的な部分だけでも)理解をすべきです。自動的なメモリ管理については問題が生じる場合もあるのです(例えば、ガベージコレクションのバグや実装の限界のような)。その議論は開発者が適切にメモリのハンドリングを行うために理解する必要があります(適切な回避策を模索するために最小限のトレードオフやコードの責務とともに)。, あなたが使用するどんなプログラム言語であろうとも、メモリのライフサイクルというものはいつも同じです。, コールスタックとメモリヒープのコンセプトについての概要は、この投稿で話題にしているので読んでみてください。, JavaScript でのメモリとは何であるかの話へと移る前に、一般的にメモリとは何なのか・端的に言うとどのように動いているのかについて話していきましょう。, ハードウェアレベルの話でいけば、コンピュータのメモリは相当数のフリップフロップ回路から成っている。どのフリップフロップも、いくつかのトランジスタを含み、1ビットの記憶領域を持っています。各々のフリップフロップはユニークな識別子でアドレスが指定できます。そのため我々は読み出したり上書きをすることが可能なのです。したがって概念的にはでありますが、コンピュータのメモリというものは読み出したり書き込んだりすることができる唯一な巨大なビット配列であると考えることができます。, ビット計算やメモリが何をしているかすべてを考えるというのは良いアイデアではないですし、人間がやることではありません。我々は数によって表現される、もう少し大きなグループにそれらを分類します。8ビットを1バイトと呼びそのバイトを超えるとワードとなるのです(それらは時に16ビットであったり、32ビットであったりします)。, コンパイラと OS は連携してメモリ管理に多くの部分を担当していますが、見えないところでどのように動いているのか見ていくことにしましょう。, コードをコンパイルする際、コンパイラはプリミティブなデータ型を調べ、それらにどのくらいメモリが必要なのか事前に計算します。必要な量がプログラムへ割当てられるわけですが、これがいわゆるスタック領域と呼ばれるものです。割り当てられたこの領域をスタック領域と呼ぶのは、関数が呼ばれた際にこれらのメモリは既存メモリの上部にスタックされるからです。それらが終了させられると後入れ先出し(LIFO)の順で除かれていきます。例えばこんな宣言を考えていきましょう:, コンパイラは直ちにこのコードが必要とするのは 4 + 4 × 4 + 8 = 28 だと分かります。, これらは、整数型・ダブル型の現サイズがどのように動くかというものです。20年前、整数は通常2バイトでダブルは4バイトでした。現時点では、何が基本のデータ型のサイズであるか依存する必要はありません。, コンパイラは、変数を格納するために、スタック上に必要なバイト数を要求するべく OS と対話するコードを挿入します。, 上記の例では、コンパイラは各変数の正確なメモリアドレスがわかっています。実際には変数nはいつでも書き換えられ、内部的には“memory address 4127963”のようなものに変換されるのです。, ここで我々はx[4]にアクセスしようとした時にmと結びついたデータにもアクセスしなければならないと気づくでしょう。なぜなら存在しない配列の要素にアクセスしようとしているからです。これは、配列の最後に割り当てられた要素x[3]よりもさらに4バイト多いからです。そしてmのビットを読み出すか(上書きして)終了し、これはほぼ確実に残りのプログラムで望まれない結果が見込めます。, 関数が他の関数を呼び出す際、自身が呼びされる際にスタックそれぞれのチャンクを取得します。そこにはすべてのローカル変数が保持されますが、実行した場所を記憶しているプログラムカウンタもあります。関数が終了すると、メモリブロックはまた他の目的に利用可能な状態となるのです。, 残念ながら、コンパイルする際ひとつの変数にどのくらいのメモリが必要なのか知るということは、それほど簡単なことではありません。下記のようなことをやろうと思った時のことを考えて見てください:, ここでのコンパイル時、ユーザによって決められるnの値は変動するため配列がどのくらいメモリを必要とするのか分かりません。, したがってスタック上の変数に空き領域を割り当てることができません。その代わり、プログラムはランタイム時に適切な領域を OS に要求する必要があるのです。このメモリはヒープ領域から割り当てられます。静的・動的なメモリの割り当ての違いは下記の表にまとめました:, 動的なメモリ割当がどのように機能するかについて完璧に理解するためには、ポインタについて多くの時間を割かなければなりません。ただしこの投稿で取り上げるには逸脱しすぎるため、もし興味が湧いて学習を進めたいならコメントでお知らせください。他の投稿でポインタについて詳細を取り上げます。, では、最初のステップであるメモリ割当が JavaScript ではどう動いているのか見ていくことにします。, JavaScript では、開発者自身は自らメモリ割当を操作する責任を負いません。JavaScript は値を宣言するだけで、言語自身がメモリ割当を行うのです。, JavaScript では割り当てられたメモリが使用されるということは、基本的にはそのメモリの中で読み書きすることを意味します。, 変数の値、オブジェクトのプロパティの値を読み書きするか、関数に引数を渡すことによって実行されます。, 割り当てられたメモリがいつ不要となるかを理解するのは、ここでは最も難しい内容です。しかし開発者は不要となったメモリがプログラムのどこにあるか適確に特定し開放する必要があるのです。, 高水準言語はガベージコレクタと呼ばれるソフトウェア(=機能)を備えています。この機能は、不要となったメモリの一部を見つけて自動的にそのメモリを開放するために、メモリの割当位置と使用を追跡します。, ただし残念なことに、このプロセスは近似なのです。なぜなら、メモリのある一部が必要であるか知るという一般的な問題は決めることが不可能だからです(アルゴリズムで解決できるものではありません)。, ほとんどのガベージコレクタはもはやアクセスされないメモリ(例えば、スコープから外れていったすべての変数など)を集めることによって動いています。しかしながら、集められた空きメモリの塊もまた近似より精度が低いものとなります。なぜなら、ある時点ではメモリ位置にまだスコープ内の変数のポインタがあったとしても、もう二度とアクセスされることはないのです。, "もう必要とされない"メモリであるかどうかを見つけるということが不可能であるため、ガベージコレクションは一般的なこういった問題を解決するための制限を実装しています。このセクションではガベージコレクションの主たるアルゴリズムと制限を理解するために必要な概念を説明していくことにします。, ガベージコレクションのアルゴリズムにおける主なコンセプトは参照の一部に依存しています。, メモリ管理のコンテキストの中では、ある1つのオブジェクトは別のオブジェクトを参照すると言われます、前者が後者に暗黙的または明示的にアクセスできるなら。例えば、JavaScript のオブジェクトはそれ自身がもつ prototype への参照を持っており(暗黙的な参照)、それ自身がもつプロパティの値への参照も持っている(明示的な参照)ことからも分かります。, この文脈において、"object" という考え方は一般的な JavaScript のオブジェクトよりもより広義のものへと拡張されており、関数のスコープ(もしくはグローバルレキシカルスコープ)ももちろん含んでいます。, レキシカルスコープはネストされた関数内で変数名がどう解決されるを定義するものです:たとえ親関数がリターンされても内部関数は親関数のスコープを含めみます, これは一番シンプルなガベージコレクションのアルゴリズムです。あるオブジェクトへの参照がゼロになったら、このオブジェクトは "ガベージコレクト可能だ" と判断するのです。, 循環参照となると限界が出てきます。下記の例の中では、2つのオブジェクトが作られて相互がもう一方を参照する形になっています。つまり、循環参照を生み出しているのです。関数が呼ばれた後にスコープから逸れていくとこれらは目的を果たして不要で解放されうるものですが、参照カウントのアルゴリズムはこの2つのオブジェクトは双方ともに少なくとも一度は参照されるものと判断し、どちらもガベージコレクトできないとみなすのです。, このアルゴリズムは、オブジェクトが必要かどうかを決めるため、オブジェクトに到達可能かを特定するアルゴリズムです。, マーク・アンド・スイープ アルゴリズム は以下の3つのステップを実行していきます:, このアルゴリズムは前述したアルゴリズムより - "ある1つのオブジェクトへの参照カウンタがゼロになった" ということはこのオブジェクトには到達できないという意味なので - 優れています。循環参照で見たように参照し合うがアクティブではないメモリを開放しないといこともありません(The opposite is not true as we have seen with cycles.