アンドロイドはLinuxカーネル(2.6.x-3.x)をベースにしたOSに、JavaVM(DalvikVM)を実装しており、複雑なメモリ管理を行っている。
スマートフォンの限られたリソース環境の中で、アプリケーションが快適に動作するように設計されている。メモリが足りなくなるとアプリが突然終了したり、処理速度が遅くなるので、一般のユーザーもメモリがどれぐらい空いているかといったことに関心があるだろう。
設定で実行中のアプリのメモリ利用量を見ることができるが、これは具体的にはどこの値を見てるのだろうか。スマートフォンのスペックから搭載されているメモリ容量はわかるが、実際利用できるメモリ量はどれぐらいだろうか。
スマートフォンのメモリ容量は増え続けているが、UI/UXの向上やデザインリソースの増加にともなってアンドロイドアプリ自体の容量も増加傾向にある。
アンドロイドのアプリケーションプログラマは、常にメモリ不足とパフォーマンス劣化に悩まされる。また非同期処理を多用しなければならないので、プログラミングは複雑だ。省メモリかつパフォーマンスのよいアンドロイドアプリを開発するには、メモリ管理は、VMにまかせておけばいいというわけにもいかない。
アプリケーションプログラマも、アンドロイドの深い知識を得る努力が必要である。まずは、自身のアプリのメモリ利用量や他のアプリのメモリ利用量を調べることで、アンドロイドのメモリ管理方法を理解する糸口としたい。
アンドロイドが利用できるメモリ量
アンドロイド端末のメモリ量は、どこを見ればわかるだろうか。スペック表のメモリ欄には、RAM/ROMのそれぞれの容量が書かれていることが多い。このRAMがメモリ量だと考えて良いだろう。
ROMはEEPROMの一種で、不揮発かつ書き換え可能なNAND型フラッシュメモリである。これは主にストレージに利用される。プログラムやデータが保存されているのが、ROMで、プログラムが実行されるときにロードされるのが、RAMだとおおざっぱに解釈していいだろう。
アンドロイド端末を持っていれば、設定のアプリケーションの管理で実行中のアプリを見れば、どこかにメモリ量を表す数値があるはずである。設定から見るメモリ量は明らかにスペックにある数字より小さいはずである。搭載しているメモリ全てが利用できるわけではない。
設定のメモリ量
たとえば、今私の手元にあるアンドロイド端末のスペックは、512M/2Gとなっている。最近では2G/32-64Gというものもあるので、今では少ないように感じる。
このアンドロイド端末の設定で、メモリ容量を見ると利用が182MBで空きが65MBとなっており、合計247MBということになる。スペック上のサイズの半分以下である。しかも、この画面をみていると、空きも利用量も刻々と変化しており、都度合計値も変わっている。
当然アプリが動作しているので、その間に利用量が変わったり、空きの量が変わるのはわかるが、合計が変わるのはなぜだろうか。正確な理由はわからないが、原因のひとつは刻々と変化することにあるのではないだろうか。ともかく、メモリ量算出とは、そう単純な話ではなさそうだというのはわかる。
APIから取得するメモリ量
他に全メモリ量を取得する方法がないか調査してみる。AndoridのAPIには以下のようなものがある。しかし、全メモリ容量を取得するには、API Level16以上のSDKを利用しなければならない。残念ながら、今手元にそのような端末がないので、この値は試していないが、いずれ試してみようと思う(13/10/31 Android4.1で試したところ、/proc/meminfoのMemTotalと一致)。
ActivityManager.MemoryInfo info = new ActivityManager.MemoryInfo(); ActivityManager am = ((ActivityManager)mContext.getSystemService(Activity.ACTIVITY_SERVICE)); am.getMemoryInfo(info); Log.d("LinuxHeap", "total[KB] = " + info.totalMem/1024); // API Level16以上で取得できる属性 Log.d(TAG, "availabled[KB] = " + info.availMem/1024);
meminfoから取得するメモリ量
一旦、これは置いておいて、さらに違う方法で全メモリ容量を調べてみる。以下のadbコマンドを実行すると、メモリ情報が取得できる。
$ adb shell cat /proc/meminfo MemTotal: 340500 kB MemFree: 7316 kB Buffers: 1496 kB Cached: 33856 kB SwapCached: 0 kB Active: 125680 kB Inactive: 154708 kB Active(anon): 116224 kB Inactive(anon): 129848 kB Active(file): 9456 kB Inactive(file): 24860 kB Unevictable: 296 kB Mlocked: 0 kB SwapTotal: 0 kB SwapFree: 0 kB Dirty: 0 kB Writeback: 0 kB AnonPages: 245344 kB Mapped: 30036 kB Shmem: 740 kB Slab: 11580 kB SReclaimable: 2812 kB SUnreclaim: 8768 kB KernelStack: 5568 kB PageTables: 19640 kB NFS_Unstable: 0 kB Bounce: 0 kB WritebackTmp: 0 kB CommitLimit: 170248 kB Committed_AS: 3203632 kB VmallocTotal: 417792 kB VmallocUsed: 180940 kB VmallocChunk: 160260 kB
meminfoから512MBのうち、340MBは利用できることがわかる。しかし、これが全てアンドロイドアプリで利用できるわけではない。
MemFreeは空き容量ということではなく、用途が決まっていない容量とでもいうべきものである。
アンドロイドのmeminfoの特徴として、通常のLinuxのMemFreeの値と比べるとアンドロイドのものは、かなり小さい。
また、file(file-backed:ファイルから読み直せばいいので、いつでも廃棄できる領域)領域よりanon(swapしなければ廃棄できない領域)領域が非常に大きい。
これはデフォルトではswapが無効となっていることが影響しているのかもしれない(SwapTotalは0)。linuxカーネルのswapを有効にするアプリ(rooted)もあるので、機能自体は存在する。
このアンドロイド端末の設定やAPIが、どのように算出しているのかわからないので、なんともいえないが、アンドロイドで利用できる全メモリ容量を取得するのも一筋縄でいかないことがわかるだろう。
どの値がほんとの利用可能メモリ容量値なのか
meminfoの情報から、カーネル以外が利用できるメモリは、Active+InActiveの合計が近似値だろうと推測する。この場合、280MB程度ではないだろうか。
Unvictableは破棄不可能という意味だが、これも加算すべきだろう。どちらにしろ、Kbyte単位の精度は難しいので、Mbyte単位ぐらいの精度を求めることを目標とする。
Active: 125680 kB Active(anon) + Active(file) Inactive: 154708 kB Inactive(anon) + Inactive(file) Unevictable: 296 kB
meminfoのMemTotalが340MBだから、60MBがカーネルの利用量とMemFreeということになるが、数字が合わない。カーネルの利用量をSlab,KernelStack,PageTables,VmallocUsedとすると、カーネルの利用量がかなり大きくなってしまうが、VmallocUsedはカーネル内のvmalloc()で確保されたもので、外部デバイスのIOメモリマッピングも含むため単純に全て加算できない。
/proc/vmallocinfoの中から、vmallocの分だけ計算してみると、12MB程度なので、カーネルの利用量は56MBとなる。おおよそ計算が合いそうだ。
MemFree: 7316 kB Slab: 11580 kB KernelStack: 5568 kB PageTables: 19640 kB VmallocUsed: 12171 kB
別のアプローチとして、動作しているアプリのメモリ利用量を合計して、空き容量を加算することで、全体容量値とすることも可能だろう。後編では、起動中のアンドロイドアプリのメモリ利用量を取得する方法の紹介と、そこから全体容量値を求めてみたい。