Bescottee苦しいときは伸びてるとき、楽なときは伸びていないとき

120 メモリリークを発見!Androidアプリのメモリ解析手法

admin to KnowHow — Tags: , , ,  



googleのAndroid開発者向け ブログに「Memory Analysis for Android Applications」という記事があったため、自分のために訳しました。参考になれば幸いです。本エントリを見るうえで、eclipse の基本的な使い方を理解している必要があります。

Androidアプリのメモリ解析手法

Dalvikランタイムは、ガベージコレクトしてくれるかもしれませんが、それはメモリ管理を行わなくてもよいというわけではありません。モバイル端末上でのメモリ利用状況は特に注意を払わなければなりません。本投稿では、開発するアプリのメモリ利用状況の把握を支援する Android SDK で提供しているメモリプロファイリングツール群のいくつかを紹介させて頂きます。

メモリ利用時の問題はいくつか明らかになっています。例えば、もしあなたのアプリがユーザの画面タッチ操作のたびにメモリリークするとすると、最終的には OutOfMemoryError のきっかけになる可能性が高いでしょう。他の問題はもっと複雑で、(あなたのアプリがガベージコレクトが頻繁に発生し、かつ長い時間発生すると)そのアプリと全体のシステムの両方のパフォーマンスを下げる原因になるかもしれません。

解析・分析のためのツール

Android SDK はアプリのメモリ利用状況をプロファイリングする手法として、2つの方法を提供します。DDMSの”Allocation Tracker”タブとヒープダンプです(heap dums)。Allocation Tracker は、与えられた時間間隔の中で発生しているメモリ割り当ての種類を知りたいときに便利です。Allocation Trackerについてもっと情報が必要な場合は、Tracking Memory Allocations を見てください。
しかし、それらはアプリケーションヒープの概要の状態に関する情報しか提供されません。本投稿の趣旨は、より強力なメモリ解析・分析のツールであるヒープの表示(dump)にフォーカスをあてていく予定です。

ヒープダンプは、HPROF と言われているバイナリフォーマットの形式で保存されるスナップショット(ある瞬間の情報(点としての情報))です。Dalvik は似たようなフォーマットを利用しますが、java の HPROF Toolとまったく同じではありません。実行中のAndroidアプリのヒープダンプを生成するいくつかの方法があります。1つは DDMS の HPROF ファイルに保存する機能です。もし、あなたがもっと正確にダンプを作成したいときはandroid.os.Debug.dumpHprofData() メソッドを利用してプログラム上からヒープダンプを作成することもできます。

ヒープダンプを解析するために、jhatEclipse Memory Analyzer (MAT)のような一般的なツールも利用できます。
しなしながら、最初に .hprof(拡張子)ファイルを Dalvik フォーマットから J2SE HPROF フォーマットに変換する必要がでてきます。
そのためにAndroid SDK の中で提供されている hprof-conv ツールを利用できます。

たとえば、

hprof-conv dump.hprof converted-dump.hprof

というコマンドで実行できます。

例:メモリリークをデバッグする

Dalvikランタイムの中で、プログラマは明示的にフリーなメモリを割り当てることはできません、そのため C や C++ の言語のように本当にメモリがリークしているかどうかを知ることができません。

ソースコード上にある”メモリリーク”は、不必要に長い間オブジェクトを参照し続けた場合に発生します。たまに 単一参照(single reference)は、大きな(大量な)オブジェクト群のガベージコレクタの実行を妨げることができます。

Android SDKの中にある Honeycomb の Gallary Sample アプリを利用して、実例を用いて理解していきましょう。これは新しいHoneycomb API群のいくつかの使い方を知るためのシンプルな写真閲覧アプリです。

ビルド方法とサンプルコードのダウンロード方法は、手順書(Android 開発者サイトのサンプルの取得方法)をご覧ください。

どのようにデバッグできるかの手法を紹介するためにこのアプリに故意にメモリリークを追加しました。


ネットワーク上から画像を取得するために、このアプリを修正したいと考えてみてください。よりレスポンスをよくするために、最近閲覧した画像を保持するキャッシュの実装を決めたとします。 ContentFragmen.java ファイルにいくつかの小さな変更を行ってみましょう。クラスの上部に新しいスタティック変数を追加しましょう。

private static HashMap<String,Bitmap> sBitmapCache = new HashMap<String,Bitmap>();

これは、ネットワークからロードするビットマップ群のキャッシュです。続いて updateContentAndRecycleBitmap() メソッドをネットワークからロードする前にキャッシュに存在するかどうかの確認を行うために変更し、ロード後にキャッシュにビットマップを追加します。

    void updateContentAndRecycleBitmap(int category, int position) {
        if (mCurrentActionMode != null) {
            mCurrentActionMode.finish();
        }

        // 描画するためのビットマップを取得し、ImageViewを更新

        // キャッシュにビットマップが存在するかどうかを確認
        String bitmapId = "" + category + "." + position;
        mBitmap = sBitmapCache.get(bitmapId);

        if (mBitmap == null) {
            // キャッシュにない場合、ビットマップをロードし、キャッシュに追加
            // 危険・注意! 常にビットマップを削除することなしにキャッシュにアイテムを追加
            mBitmap = Directory.getCategory(category).getEntry(position)
                .getBitmap(getResources());
            sBitmapCache.put(bitmapId, mBitmap);
        }
        ((ImageView) getView().findViewById(R.id.image)).setImageBitmap(mBitmap);
    }

私はここで故意にいれたメモリリークを紹介します。常にキャッシュを削除することなしに ビットマップをキャッシュに追加します。実際にアプリでこのような実装する場合は、いくつかの方法でキャッシュサイズに制限をかけたいを考えるでしょう。

DDMSを利用してヒープの利用状況を確認

Dalvik Debug Monitor Server (DDMS) は、主要なAndroidデバッグツールの1つです。DDMSは ADT Eclupse プラグインの一部で、単体動作版はAndroid SDKの tools/ ディレクトリの中にあります。DDMSに関する情報をもっと知りたい場合は、DDMSを利用してみましょう。

それでは、このアプリのヒープ利用状況を確認するためにDDMSを利用しましょう。DDMSを起動する方法は2つあり、そのどちらでも起動できます。

  • Eclipse上からメニューバーを以下の順序で選択する方法

Windows > Open Perspective > Other … > DDMS

  • コマンドライン上から tools/ ディレクトリの中にある ddms を起動する。

(追記:Windowsの場合は、ddms.bat が起動され、Mac/Linux の場合は、ddmsが起動される。(それぞれに PATH を通している必要があります))

左のパネルからcom.example.android.hcgalleryプロセスを選択して、ツールバー上の”Show heap updates”ボタンをクリックします。
それから DDMS の VM Heap タブに切り替えます。

ヒープメモリ利用状況について GC毎に更新されるいくつかの基本情報が見れます。最初の更新を行うために、”Cause GC”ボタンをクリックします。

我々は live setという(割り当てメモリ列)8MBを少し超えるものをみることができます。サンプルアプリの写真をフリックすると、数字が増加していくのが見れます。このアプリは13枚の写真しかありませんが、リークしたメモリの容量が割り当てられていきます。このような場合リークの最悪のパターンが訪れます。しかしながら我々は決して リークすることにより OutOfMemoryError を起こしてはいけません。

ヒープダンプ(heap dumps)を作成

問題を追うためにヒープダンプを作ってみましょう。DDMSツールバーのDump HPRPF fileボタンをクリックして、保存場所を選択して、それから hprof-conv でコンバートしてください。この例では、MAT(Ver 1.0.1)のスタンドアローンバージョン(Stand alone version)を利用しています。これはMATダウンロードサイトで提供されています。

もし、ADT(DDMSのプラグインの親元)が実行している場合や Eclipse に MAT がインストールされている場合は、”dump HPROF”ボタンを押すと自動的にコンバート(hprof-convを実行)してくれ、Eclipse内(MATでオープンされる)で変換された hprof ファイルを開いてくれます。

MATを利用したヒープダンプの解析・分析

MATを実行し、いま先ほど作成したコンバート済みの HPROF ファイルをロードしてください。(上記のように必要なツールがインストールされていれば、自動的にコンバートとMAT起動を行ってくれます)。MATは強力なツールですべての機能を説明するためには本投稿の範囲を超えてしまいます。そのためいまはリークを発見するために利用する1つの方法として、Histogram View(ヒストグラムビュー)を紹介します。ヒストグラムビューはインスタンスの数でソートされたクラスのリストと表面上のヒープ(表層ヒープ:shallow heap)(すべてのインスタンスで利用しているメモリの総合計)と保持されたヒープ(保持ヒープ:retained heap)(他のオブジェクトへの参照をもっているオブジェクトを含めたすべてのインスタンスで生きているメモリの総合計)を表示します。

もし shallow heap でソートすれば、byte配列のインスタンスが上部に表示されます。Android 3.0(ハニカム)の場合、ビットマップオブジェクトのピクセルデータは byte配列に保存されます。(以前は Dalvik heap に保存されませんでした)
そして、それらのオブジェクトのサイズを参考にして、メモリリークしたビットマップのための裏にあるメモリだと賭けても(bet)あっている(safe)でしょう。

byte[] クラス上で右クリックで押して、
List Objects → incoming references
を選択します。これは、ヒープの中に byte 配列のすべてをリストで表示したもので、Shallow Heapの利用状況をベースにソートすることのできます。

大きなオブジェクトの一つを選び、ドリルダウンしていきましょう。これは、オブジェクトのrootからの path を見せてくれます。生存しているオブジェクト参照のつながりを表しています。ほら、我々のビットマップキャッシュがありますね。

MATはこれがリークかどうかを教えてはくれません。なぜなら、それが必要なものか、そうでないかがわからないためです。それはプログラマのみができることなのです。今回の場合、キャッシュは比較的アプリの休憩中(ユーザが操作していない場合)にメモリの大きな容量を使っています。そのためキャッシュのサイズの上限を検討しないといけません。

MATでヒープダンプを比較

メモリリークをデバッグしているとき、2つの異なったポイントのヒープ利用状況を比較すると、とても有益です。それを行うために、2つの異なる HPRPOF ファイルを作成する必要があります。(hprof-convを使ってコンバートは忘れずに!)

では、ここでどうやって MAT で2つのヒープダンプを比較すればよいかをお伝えします。(少し重ねればよいのです)

  1. 最初のHPROFファイルを開く(Fie > Open Heap Dump)
  2. ヒストグラムビュー(Histogram view)を開く
  3. Navigation History view(eclipe のメニューのWindow > Navigation History。なければ Window > Other… > Memory Analyzer Views < Navigation Historyで選択)の中の histogram を選択し、右クリックして「Add to Compare Basket」を選ぶ
  4. 2つ目の HPROF ファイルをステップ2と3を繰り返してください
  5. 「Compare Basket view」に切り替えて、Compare the Results(Compare Basket viewの上部の右上にある赤い!アイコン)をクリック

結論

本投稿ではどのように allocation Tracker と ヒープダンプ(heap dumps)はアプリのメモリ利用状況のよりより見え方・感じ方を得るための方法を紹介しました。私はまた どのよに Eclipse Memory Analyzer(MAT) がアプリのメモリリークを追う方法を紹介しました。MATは強力なツールであり、私もこのツールで何ができるかの表面しか紹介しませんでした。

もしあなたがよりもっと学びたいとお考えなら、以下のURLのいくつかを読むことをお勧めします。

メモリリークを避けるためにお薦め本

Effective Java 第2版

まだお持ちでないなら、メモリリークを避けるためにも必読です。

増補改訂版Java言語で学ぶデザインパターン入門

先人の知恵であるパターンを利用することによって大幅にメモリリークを避けられるようになります。

増補改訂版 Java言語で学ぶデザインパターン入門 マルチスレッド編

マルチスレッド環境ではメモリリークが発生しやすくなります。マルチスレッドを扱う場合は必読です。


120件のコメント »

  1. hiroyuki sato より:

    メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee – http://andbrowser.com/develop...

  2. わかめ より:

    メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee – http://andbrowser.com/develop...

  3. eimei23 より:

    なんつーか、Javaのメモリ管理しなくて良いというのは全くのまやかし。Objective-Cよりわかりづらい分ハードな気がするなあ。 " メモリリークを発見!Androidアプリのメモリ解析手法 | Bescottee http://t.co/mhxuZokZ "

  4. kotaro-ono より:

    メモリリーク発見。ヒープダンプ。

  5. tkido より:

    “メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee” http://t.co/2IVQMq73

  6. taclose より:

    メモリーリークの見つけ方#android
    http://t.co/pECFZ1zJ

  7. yoktys より:

    メモリリークを発見!Androidアプリのメモリ解析手法 http://t.co/NpaqoBD8

  8. sigeharucom より:

    Androidアプリのメモリリーク対策手法 | Bescottee http://t.co/gvOzNErs #androiDev

  9. gom より:

    Androidアプリのメモリリーク対策手法 | Bescottee – http://andbrowser.com/develop...

  10. gom より:

    メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee – http://andbrowser.com/develop...

  11. reminder5 より:

    メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee http://t.co/Ojsp6hzC http://t.co/OyqwYzOI 6565

  12. nagise より:

    “メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee” http://t.co/v5lOq6kn

  13. [...] メモリリークを発見!Androidアプリのメモリ解析手法 [...]

  14. nilab より:

    メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee : 「DDMSの”Allocation Tracker”タブとヒープダンプです(heap dums)」

  15. yamanetoshi より:

    メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee – http://andbrowser.com/develop...

  16. Akihiro Kitada より:

    . @quitada Eclipse の Memory Anaylyzer で、複数のヒープダンプのオブジェクト数とか比較するビューをだす方法
    http://t.co/dV2bDSH1uO
    >ともあれ、Δ値をだしてくれるわけではなさそう(単純に並べて表示するビュー)

  17. Y..a より:

    Androidアプリのメモリリーク対策手法 | Bescottee http://t.co/JoCIpZnbwg

  18. reminder より:

    メモリリークを発見!Androidアプリのメモリ解析手法 « Bescottee http://t.co/OrOfwRMf76 http://t.co/mkOn1U0Y4I 8934

  19. [...] メモリリークを発見!Androidアプリのメモリ解析手法 [...]

このコメント欄の RSS フィード トラックバック URL

コメントをどうぞ