2012年03月09日

android のメモリ管理と出会う旅 [前編]

 いやっほう。6年ぶりの新しいデジカメ が届いたよ!。でも今日は天気悪すぎなせいもあるけど、えらいノイジーで嬉しくない写真ばっか撮れるんですが、えーこんななの?。晴れてれば嬉しい色で撮れますように。でも広角はすっごい嬉しい。ダムが捗るね!

 で、なんかすっかりブログも書かない残念な子になりつつありますが、やっとひと段落したのでメモリとの戦いの記録でもまとめておきますか。

判断は俺がするから一覧よこせ

 なんかね、「android メモリリーク」 とかでぐぐると 「Memory Analyzer を使えばメモリの悩みは何でも解決だよ」 みたいな記事がたくさん出てくるんですが、どうにも僕が知りたい事は人とズレてるようで、しっくり来ません。

 Memory Analyzer は、現在 android 上で動いてる全プロセスが使っているメモリを調査して、その中から 「このアプリメモリ食いすぎじゃね?」 とか 「同じようなメモリ2つあるけど破棄してなくね?」 とかメモリリークと疑わしい場所を勝手に判断して教えてくれます。
 判断までしていただいてタイヘン有難い事なのですが、問題は基本的にOSの全メモリに対しての調査しかしてくれないことです。android は大量のプロセスが裏で動いてますので、自分には不要な情報が多すぎてレポート読む気にもなりません。

 そもそも僕は今はリークを知りたい訳じゃなくて、ガーベジコレクタ(GC)が本当にメモリを始末してくれてるのか、どのようなコードを組むと残ってしまうのかをトライ&エラーで調べたかったので、「今現在、自分のアプリが使用しているメモリの一覧」 が欲しいだけだったんですが、そういう人はあまり居ないようなので、その方法を模索してみました。

 まずはどのみち、Memory Analyser は必要なのでインストールしてください。‥‥と、一言で済ますページが多かったのですが、地味にめんどいです。こちらの記事で親切に解説されていました。  ここで罠があるのですが、最近の AndroidSDK では、メモリをダンプした時に自動的に Memory Analyzer を起動せず、まずファイルに保存しようとします。大迷惑です。設定を変えておきましょう。
 [ウインドウ] > [設定] > [Android] > [DDMS] の [HPROFアクション] を [Open in Eclipse] に変更。
20120309a.png

そいでは調べてみよう

 では実際にメモリがどれだけ確保されているのか調べてみましょう。自分のアプリをデバッガで起動したら、メモリを調べたいタイミングで [Devices] ビューの [Dump HPROF File] ボタン を押します。
20120309b.png
 すると、まず現在使用されている全メモリが HPROF ファイルにダンプされ、その後、そのファイルを自動的に Memory Analyzer が解析を始めます。
 なお、この処理は 実機だと数秒なのですが、エミュレータだと30秒くらい無反応で待たされます。話になりません。やっぱ android の開発は実機がないと厳しいですな。

 Memory Analyzer が解析を終えると、下のダイアログが出て、レポート見たい?と聞いてきます。素直に一番上を選んで一度レポートを読んでみるのもいいと思いますし、僕の場合はレポートは要らないので一番下のチェックを外して出さなくしちゃいました。
20120309c.png
 少し待つとレポートが表示されます。一生懸命まとめてくれた物を無視するのも心苦しいのですが、気にせず [Open Dominator Tree for entire heap] ボタン を押して、現在確保されているメモリの一覧を表示します。
20120309d.png
 現在確保されているオブジェクト(つまりはメモリ)の一覧が表示されます。膨大な数です。とりあえずサイズの大きい物から順に並べてくれてますが、9割方は他のアプリが確保した無関係な物なので、<Regex> のところに自分のパッケージ名を入力して、自分に関係ある物だけに絞りましょう。
20120309e.png
 ちなみに、一覧の、「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回連打してからダンプするようにすると、謎のオブジェクトで悩む事が少なくなります。

 といったところで、とりあえずやっと自分の確保したメモリをいつでも調べられるようになったので、あとはこれを使って、必要ないメモリが残りにくい設計を模索していくことにしましょう。
 長くなったので続きはまた明日

posted by ひこざ at 23:52| Comment(3) | 開発 - Android
この記事へのコメント
お久しぶりです。超ご無沙汰してます。
1月ほど前に思い付きからCycropseダウンしに来てページが変わってるのに気付き。暇な仕事中にのぞいてました。
で、Java関係みてて、ケロッグコンボ張りに我慢できな〜いってことでつい書き込んでしまいました。
上にあるようなことくらいなら相談に乗れるのでどうぞ。
というか、久しぶりに飲みながらでもダメなことでも話したいです。
Posted by ひて° at 2014年11月07日 21:38
おわあー、お久しぶりです。びっくりした。

この内容についてはあまりに昔過ぎなので、またゼロから勉強しなおさないとダメな気はしますが、久しぶりに飲みたいは全面的に同意です。

ちょっと今仕事やら何やら立て込んでるんですが、落ち着いたら連絡します。

それにしてもなぜ今サイクロップス
Posted by ひこざ at 2014年11月08日 09:25
サイクロップスって、一人称画面だと勝手に思い込んでて(やってなかったのばれたな)、nVidia3D Vision2とHMZ使うと「俺の目からビームが止まらねぇ」ってできる夢を見ました。

あと、連絡先はみんなに聞かれるけど、昔のアドレスから
xxx_yyy@docomo〜 ⇒ xxx.yyy49@gmail〜 変換で。
Posted by ひて° at 2014年11月08日 09:46
コメントを書く
お名前: [必須入力]

メールアドレス:

ホームページアドレス:

コメント: [必須入力]

認証コード: [必須入力]


※画像の中の文字を半角で入力してください。