2013 年 9 月 7 日

アンドロイドアプリのメモリ容量を取得する(後編)

3041-logo

前編では、/proc/meminfoの内容を元にアンドロイドで利用できるメモリ量の求め方を考察した。後編では、動作している各アプリのメモリ利用量の取得方法と、そこから利用可能メモリ量を推察してみたい。

アンドロイドアプリの起動はzygote(DVMのデーモン)が実行する。zygoteから起動(fork)された各アプリが必要なメモリを占有するが、共通部分まで、アプリごとに占有してしまうとあっという間にメモリが不足してしまう。

そこで、COW(Copy On Write)と呼ばれる手法で、起動時はzygoteとメモリ空間を共有している(shared)。その後、必要になれば、アプリ専用のメモリ(private)に置き換わる。

それに加えて、各アプリ内では、必要のなくなったメモリを回収するガベージコレクションを実行し、空きメモリを確保しようとする。できるだけメモリを効率よく扱うために、非常に複雑なメモリ管理が行われている。

各アンドロイドアプリが利用しているメモリ量

各アンドロイドアプリが利用しているメモリ量は以下ようなプログラムで取得できる。

    ActivityManager activityManager = (ActivityManager)mContext.getSystemService(Context.ACTIVITY_SERVICE);
    List<RunningAppProcessInfo> runningApps = activityManager.getRunningAppProcesses();
    int pids[] = new int[runningApps.size()];
    String processNames[] = new String[runningApps.size()];
    int i = 0;
    for(RunningAppProcessInfo app : runningApps) {
      pids[i] = app.pid;
      processNames[i] = app.processName;
      ++i;
    }
    android.os.Debug.MemoryInfo[] memInfos = activityManager.getProcessMemoryInfo(pids);
    long totalPss = 0;
    i = 0;
    for (android.os.Debug.MemoryInfo info : memInfos) {
      Log.d(TAG, "[" + processNames[i] + "] total pss[KB] = " + info.getTotalPss());
      ++i;
      totalPss += info.getTotalPss();
    }
    Log.d(TAG, "TOTAL pss[KB] = " + totalPss);

サービスのメモリ使用量(13/10/20追記)

上記のリストには実行中サービスのメモリ量が含まれていない。より正確なメモリ使用量を計算するには、サービスの分も付加する必要がある。実行中サービスのパッケージ名とプロセスIDを取得する方法は以下の通りである。

    List<ActivityManager.RunningServiceInfo> services = activityManager.getRunningServices(100);
    if (services != null) {
      for (int i = 0; i < services.size(); ++i) {
        ActivityManager.RunningServiceInfo service = services.get(i);
        Log.d(TAG, "app:service: pkg = " + service.service.getPackageName() + " pid = " + service.pid);
      }
    }

各アプリのメモリ利用量の情報はandroid.os.Debug.MemoryInfoに格納されているが、このクラスの構造は以下のようになっている。

public int	dalvikPrivateDirty	The private dirty pages used by dalvik.
public int	dalvikPss	The proportional set size for dalvik.
public int	dalvikSharedDirty	The shared dirty pages used by dalvik.
public int	nativePrivateDirty	The private dirty pages used by the native heap.
public int	nativePss	The proportional set size for the native heap.
public int	nativeSharedDirty	The shared dirty pages used by the native heap.
public int	otherPrivateDirty	The private dirty pages used by everything else.
public int	otherPss	The proportional set size for everything else.
public int	otherSharedDirty	The shared dirty pages used by everything else.

アンドロイドアプリのヒープには二種類あって、Dalivikヒープとネイティブヒープに大きく分類される。dalvikはDalvikヒープのメモリ量で、nativeはネイティブヒープのメモリ量で、otherはその他のヒープのメモリ量である。

それぞれにSharedとPrivateの量があるが、Sharedとは他のプロセスと共有しているメモリ量で、Privateは自身のプロセスで占有しているメモリ量である。

また、Dirty(これに対してCleanと呼ばれるものがある)は、swapしなければ廃棄できないメモリ量である。

APIからではなく、dumpsysコマンドでこれらの値を取得することもできるので、参考に記しておく。こちらの方がさらに詳しい情報が得られる。量が多くなるので、リダイレクトしてファイルに保存するとよいだろう。

adb shell dumpsys meminfo > c:\temp\meminfo.txt

では、いったいどの値が、このプロセス(アプリ)のメモリ利用量だろうか。上記の値にPssがつくものがある。Pssとは、Proportional Set Sizeの略で、複数プロセスで共有しているメモリをプロセス数で案配したものである。

Pssの値は、プライベートで占有しているメモリ量+共有しているメモリ量(プロセス数で案配したもの)が含まれている。よって、dalivkPss+nativePss+OtherPssの合計が、このプロセスのメモリ利用量だと考えていいだろう。

その値は、getTotalPss()で取得できるので、この量を全て加算すれば、現在利用しているメモリ量となる。

上記のサンプルプログラムを動作されると、246MBぐらい利用しているようだ。前編で紹介したAPIによる空きは30MBほどなので、アンドロイドで利用できる全メモリ量は276MBとなり、前編でmeminfoから求めた値にもかなり近い。

TOTAL pss[KB] = 246801

前編と後編で2回に分けて、アンドロイドのメモリ量を計算してみた。MB単位ぐらいの精度を求めても、やはり多少誤差が生じる。効率よくメモリを利用するためにOSやVMが行っている複雑な処理を考えると、常時メモリ量に変動がある中、正確なメモリ量を量るのは非常に難しいことが理解できるだろう。