【JavaScript】メモリの浪費を避けるコーディング

JavaScript

こんにちは。良昌です。

Facebook、GithubなどのJSON形式でユーザ情報を返却するAPIや、PhoneGap、Monacaなどのスマートフォンのマルチプラットフォームを提供するライブラリ、IDEが開発ツールとして定着してきたことにより、JavaScriptを利用する機会が増えたのではないでしょうか。

今回は、JavaScriptコンテナがWebブラウザの場合に、動的に確保されるメモリ領域の浪費を避ける方法について書きたいと思います。

JavaScriptにおけるメモリの浪費を避けるコーディング

JavaScriptにおけるメモリの浪費を避けるコーディングをするためには、GC(ガベージコレクション)、クロージャについての知識が必要です。まずは、この2つの機能について説明していきます。


■GC(ガベージコレクション)

GCとはプログラムが動的に確保したメモリ領域の内、不要になった領域を自動的に開放する機能です。各Webブラウザに実装された処理系(以下、JavaScriptエンジン)は、対象のオブジェクトにより2つのGC方式(ガベージコレクタ、参照カウンタ)を使い分けています。

・ガベージコレクタ

JavaScriptオブジェクトについては、ガベージコレクタ方式が採用されています。ガベージコレクタは以下のタイミングでメモリの開放を行います。

  • 変数に対し、明示的にnullをセットした時
  • 関数の実行終了時
  • 上記以外は任意のタイミング(JavaScriptエンジンによっては、ガベージコレクタを呼出すメソッドが用意されている)

・参照カウンタ

DOMオブジェクトについては、参照カウンタ方式が採用されています。参照カウンタ方式とは、オブジェクトの参照が発生する度に記録される整数値を監視しておき、この整数値が0になったタイミングでメモリの開放を行う仕組みです。


■クロージャ

クロージャを説明するにあたって、良くみかけるのは以下のようなコードです。

「これがクロージャです!!!!」と書いてあるのですが、「???」と思う方が多いのではないでしょうか。確かにこれはクロージャを利用したコードですが、説明としては不十分だと思います。

ここではアクティベーションオブジェクト、及びスコープチェーンと呼ばれるJavaScriptの仕様について説明しながら、クロージャについての理解を深めてもらいたいと思います。

・アクティベーションオブジェクト

アクティベーションオブジェクトとは関数のコールが発生した際に、自動的に生成されるオブジェクトです。アクティベーションオブジェクトには、引数、ローカル変数だけでなく、argumentsオブジェクト、thisが格納されます。

・スコープチェーン

スコープチェーンとは、変数の参照を解決する際にアクティベーションオブジェクトを辿る仕組みです。JavaScriptでは変数の参照を解決する手順として、最初に自スコープ内のアクティベーションオブジェクトを検索します。そこで該当する変数が見つからなかった場合は、1つ外側のスコープにあるアクティベーションオブジェクトから対象の変数を検索します。それでも見つからない場合は、更に外側のスコープにあるアクティベーションオブジェクトを検索します。

アクティベーションオブジェクトはGCの対象となっているので、通常は関数の実行終了時にメモリ上から解放されます。ただし、対象の関数がメモリ上に生存している間は保持され続けます。上記の場合は18行目でグローバル変数であるmogeに関数hoge()が代入されているため、mogeに対し、明示的にnullをセットしない限り、hoge()内部のアクティベーションオブジェクトは保持され続けます。

この性質を利用して、スコープチェーンによりローカル変数の値を参照し続けるデータ構造を、クロージャと呼びます。

ここまでがGC、クロージャについての説明となります。以降は、本題であるメモリの浪費を避けるコーディングについて、いくつか例を挙げて説明していきます。


■必要が無い場合はクロージャの利用を避ける

クロージャは非常に便利な仕組みですが、メモリを浪費する原因になり兼ねません。場合によってはメモリリークを引き起こす場合もありえますので、必要が無い場合はクロージャの利用を避けるべきです。

以下は、クロージャを利用すべきでないケースです。

上記のコードは、関数がコールされる度にスコープチェーンが生成され、メモリを浪費します。クロージャにする利点もありません。このような場合は、以下のようにprototypeを利用してメンバを定義します。


■循環参照によるメモリリークを避ける

循環参照とは、あるオブジェクトを辿っていくと自分自身に行き着くケースを指します。DOMオブジェクトについては参照カウンタ方式が採用されているため、循環参照が発生すると参照カウンタが0にならず、ブラウザを閉じるまでメモリが解放されずに残ってしまいます。

以下は、循環参照によるメモリリークが発生するコードです。

elemはfuga内のelem(3行目)からスコープチェーンで参照され、fugaはaddEventListener(6行目)によってelemから参照されているため、fuga -> elem -> fugaという循環参照が発生しています。

これを回避するためには、単純にfugaをグローバルスコープにしてあげればOKです。グローバルスコープにすることで、スコープチェーンによる参照が無くなるため、循環参照は発生しなくなります。

また、どうしてもクロージャを利用したい場合は、循環参照を明示的に切る必要がありますので、以下のように実装します。

分かりずらいかも知れませんが、3行目で引数fooに対し明示的にnullをセットしているため、fuga -> elm -> moge -> foo -> nullとなり、循環参照が切られています。

複数のイベントリスナに対応させるためには、mogeに代入された無名関数を以下のように修正してあげれば良いです。


■まとめ

JavaScriptは非常に自由度の高い言語である故、意図せずメモリが浪費されるコードになりがちです。最近のPCは安価でもスペックが高いため、クライアントリソースの消費について深く考えることはそれ程無いと思いますが、これを機に見直してみることも良いかと思います。

では、また。

4 件のコメント

  • […] [JavaScript]メモリの浪費を避けるコーディング by Utage Blogこんにちは。良昌です。 Facebook、GithubなどのJSON形式でユーザ情報を返却するAPIや、PhoneGap、Monacaなどのスマートフォンのマルチプラットフォームを提供するライブラリ、IDEが開発ツールとして定着してきたことにより、JavaScriptを利用する機会が増えたのではないでしょうか。 今回は、JavaScriptコンテナがWebブラウザの場合に、動的に確保されるメモリ領…続きを読む   みんなのコメント    […]

  • […] [JavaScript]メモリの浪費を避けるコーディング by Utage Blog こんにちは。良昌です。 Facebook、GithubなどのJSON形式でユーザ情報を返却するAPIや、PhoneGap、Monacaなどのスマートフォンのマルチプラットフォームを提供するライブラリ、IDEが開発ツールとして定着してきたことにより、JavaScriptを利用する機会が増えたのではないでしょうか。 今回は、JavaScriptコンテナがWebブラウザの場合に、動的に確保されるメモリ領… […]

  • コメントを残す

    メールアドレスが公開されることはありません。

    ABOUTこの記事をかいた人

    WEB・スマートフォンを中心に事業を展開。Seasar2を拡張した独自のJavaフレームワークを開発し、自社プロダクトを現在、開発中。2007年、株式会社ヘッドウォータース中途入社。