で、なんかすっかりブログも書かない残念な子になりつつありますが、やっとひと段落したのでメモリとの戦いの記録でもまとめておきますか。
判断は俺がするから一覧よこせ
なんかね、「android メモリリーク」 とかでぐぐると 「Memory Analyzer を使えばメモリの悩みは何でも解決だよ」 みたいな記事がたくさん出てくるんですが、どうにも僕が知りたい事は人とズレてるようで、しっくり来ません。Memory Analyzer は、現在 android 上で動いてる全プロセスが使っているメモリを調査して、その中から 「このアプリメモリ食いすぎじゃね?」 とか 「同じようなメモリ2つあるけど破棄してなくね?」 とかメモリリークと疑わしい場所を勝手に判断して教えてくれます。
判断までしていただいてタイヘン有難い事なのですが、問題は基本的にOSの全メモリに対しての調査しかしてくれないことです。android は大量のプロセスが裏で動いてますので、自分には不要な情報が多すぎてレポート読む気にもなりません。
そもそも僕は今はリークを知りたい訳じゃなくて、ガーベジコレクタ(GC)が本当にメモリを始末してくれてるのか、どのようなコードを組むと残ってしまうのかをトライ&エラーで調べたかったので、「今現在、自分のアプリが使用しているメモリの一覧」 が欲しいだけだったんですが、そういう人はあまり居ないようなので、その方法を模索してみました。
まずはどのみち、Memory Analyser は必要なのでインストールしてください。‥‥と、一言で済ますページが多かったのですが、地味にめんどいです。こちらの記事で親切に解説されていました。 ここで罠があるのですが、最近の AndroidSDK では、メモリをダンプした時に自動的に Memory Analyzer を起動せず、まずファイルに保存しようとします。大迷惑です。設定を変えておきましょう。
[ウインドウ] > [設定] > [Android] > [DDMS] の [HPROFアクション] を [Open in Eclipse] に変更。
そいでは調べてみよう
では実際にメモリがどれだけ確保されているのか調べてみましょう。自分のアプリをデバッガで起動したら、メモリを調べたいタイミングで [Devices] ビューの [Dump HPROF File] ボタン を押します。 すると、まず現在使用されている全メモリが HPROF ファイルにダンプされ、その後、そのファイルを自動的に Memory Analyzer が解析を始めます。なお、この処理は 実機だと数秒なのですが、エミュレータだと30秒くらい無反応で待たされます。話になりません。やっぱ android の開発は実機がないと厳しいですな。
Memory Analyzer が解析を終えると、下のダイアログが出て、レポート見たい?と聞いてきます。素直に一番上を選んで一度レポートを読んでみるのもいいと思いますし、僕の場合はレポートは要らないので一番下のチェックを外して出さなくしちゃいました。 少し待つとレポートが表示されます。一生懸命まとめてくれた物を無視するのも心苦しいのですが、気にせず [Open Dominator Tree for entire heap] ボタン を押して、現在確保されているメモリの一覧を表示します。 現在確保されているオブジェクト(つまりはメモリ)の一覧が表示されます。膨大な数です。とりあえずサイズの大きい物から順に並べてくれてますが、9割方は他のアプリが確保した無関係な物なので、<Regex> のところに自分のパッケージ名を入力して、自分に関係ある物だけに絞りましょう。 ちなみに、一覧の、「Shallow Heap」 はそのオブジェクト単体のメモリサイズ、「Retained Heap」 はそのオブジェクトが内部で確保したオブジェクトも含めた総サイズのようです。
自分のパッケージに絞れば、自分で作った見覚えのあるクラスの名前が並ぶはずなので、何がどれだけメモリを消費しているのか確認できるかと思います。大きく3種類に分けられます。
- java.lang.String から始まる物
- strings.xml などで定義した、アプリ名などのリソースの文字列と思います。アプリが起動する以上は必要な物なので、気にしても仕方ありません。無視。
- 末尾が System Class の物
-
クラスのスタティック領域です。クラスを一度でも利用すると、その時に確保され、アプリのプロセスが終了するまではまず始末されません。static 変数が1つもないなら 0 になります。
android では一度起動したアプリのプロセスはメモリ不足になるまで始末されないので、つまりは static 変数を多用するクラスは、アプリの使用後もいつまでも地味にメモリを消費する事になります。といっても、メモリが不足したら人知れず始末されるだけなので、残ってても気にする必要はないんですけどね、気分の問題です。
static 変数は設計方針で減らせるけど、final にした定数でも同じようにメモリを食うようなので、なんか気分的に定数使いたくなくなりますね。#define 使わせてくれよう。 - それ以外の物
-
new で確保されたインスタンスです。今使ってるはずの物しか表示されてないなら何の問題もないですが、もう要らないはずの物が残っていたら、何か意図しない参照が残っていて始末できないのかもしれませんし(いわゆるメモリリーク)、単にGCの都合で要らないのに残っているだけかもしれません。
メモリダンプするボタンの右隣にある、ゴミ箱のボタンはGCに働けと催促するボタンですので、ダンプの前に2、3回連打してからダンプするようにすると、謎のオブジェクトで悩む事が少なくなります。
といったところで、とりあえずやっと自分の確保したメモリをいつでも調べられるようになったので、あとはこれを使って、必要ないメモリが残りにくい設計を模索していくことにしましょう。
長くなったので続きはまた明日。