Androidベンダー向けメモ: LowMemoryKiller の仕組み

LowMemoryKiller によるプロセス kill の優先順位の仕組み

Android 独自の仕組みである Low Memory Killer は、空きメモリが一定以下になると、アプリプロセスを自動的に kill します。 実体は下記のカーネルに含まれているネイティブプログラムです。

  • kernel/drivers/staging/android/lowmemorykiller.c

どのプロセスが kill されるかは、プロセスごとに設定された adj の値によって決まります。 この値が小さい方が優先度の高いプロセスであることを示し、Low Memory Killer によって殺されにくくなります。 メモリサイズと、adj の関連は、init.rc の中で以下のような感じで設定されます。

# Write value must be consistent with the above properties.
write /sys/module/lowmemorykiller/parameters/adj 0,1,2,3,4,5,6,7,14,15
write /sys/module/lowmemorykiller/parameters/minfree 2048,4096,8192,8192,16384,20000,25000,30000,35000,40000

上記の設定では、空きメモリが 8192 page (= 8192 * 4K = 32768K) 以下になったときに、adj が 3 以上であるプロセスが kill されることを示しています。 adj の具体的な値として、以下のようなものがあらかじめ定義されています (Honeycomb)。

ActivityManagerService 内変数System propertyDefaultDescription
SYSTEM_ADJ-16The system process runs at the default adjustment.
CORE_SERVER_ADJ-12This is a process running a core server, such as telephony. Definitely don’t want to kill it, but doing so is not completely fatal.
FOREGROUND_APP_ADJro.FOREGROUND_APP_ADJ0Foreground app
VISIBLE_APP_ADJro.VISIBLE_APP_ADJ1Visible app
PERCEPTIBLE_APP_ADJro.PERCEPTIBLE_APP_ADJ2This is a process only hosting components that are perceptible to the user, and we really want to avoid killing them, but they are not immediately visible. An example is background music playback.
HEAVY_WEIGHT_APP_ADJro.HEAVY_WEIGHT_APP_ADJ3This is a process with a heavy-weight application. It is in the background, but we want to try to avoid killing it.
SECONDARY_SERVER_ADJro.SECONDARY_SERVER_ADJ4This is a process holding a secondary server – killing tit will not have much of an impact as far as the user is concerned.
BACKUP_APP_ADJro.BACKUP_APP_ADJ5This is a process currently hosting a backup operation. Killing it is not entirely fatal but is generally a bad idea.
HOME_APP_ADJro.HOME_APP_ADJ6This is a process holding the home application – we want to try avoiding killing it, even if it would normally be in the background, because the user interacts with it so much.
HIDDEN_APP_MIN_ADJro.HIDDEN_APP_MIN_ADJ7This is a process only hosting activities that are not visible, so it can be killed without any disruption.
ro.CONTENT_PROVIDER_ADJ14
EMPTY_APP_ADJro.EMPTY_APP_ADJ15This is a process without anything currently running in it. Definitely the first to go!
HIDDEN_APP_MAX_ADJro.EMPTY_APP_ADJ15
(GTV)ro.CHROME_CRITICAL_OOM_ADJ0
(GTV)ro.CHROME_HIGH_OOM_ADJ5
(GTV)ro.CHROME_MEDIUM_OOM_ADJ5
(GTV)ro.CHROME_LOW_OOM_ADJ6

プログラム内で参照する adj に関するシステムプロパティは、init.rc で以下のような感じで設定されています。

on boot
    ...
    setprop ro.FOREGROUND_APP_ADJ 0
    setprop ro.VISIBLE_APP_ADJ 1
    setprop ro.PERCEPTIBLE_APP_ADJ 2
    setprop ro.HEAVY_WEIGHT_APP_ADJ 3
    setprop ro.SECONDARY_SERVER_ADJ 4
    setprop ro.BACKUP_APP_ADJ 5
    setprop ro.HOME_APP_ADJ 6
    setprop ro.HIDDEN_APP_MIN_ADJ 7
    setprop ro.CONTENT_PROVIDER_ADJ 14
    setprop ro.EMPTY_APP_ADJ 15
    ...

動作中のプロセスに対してどのような adj 値が割り当てられるかは、com.android.server.am.ActivityManagerServicecomputeOomAdjLocked() で計算されます。 このメソッドを呼び出した後に、パラメータで渡した ProcessRecord オブジェクトの内容(curAdjadjType フィールドなど)をダンプすれば、そのプロセスがどのように評価されたかが分かります。

Log.i("HOGE", "[" + i + "] " + app.processName + "(pid=" + app.pid + ", uid=" + app.info.uid + ")");
Log.i("HOGE", "        curAdj=" + app.curAdj + ", adjType=" + app.adjType +
              ", hidden=" + (app.hidden ? 1 : 0) + ", keeping=" + (app.keeping ? 1 : 0));

出力結果

08-30 10:07:30.023   433   451 I HOGE    : [25] com.android.inputmethod.latin(pid=553, uid=10046)
08-30 10:07:30.023   433   451 I HOGE    :         curAdj=1, adjType=service, hidden=0, keeping=1
08-30 10:07:30.023   433   451 I HOGE    : [24] system(pid=433, uid=1000)
08-30 10:07:30.023   433   451 I HOGE    :         curAdj=-16, adjType=fixed, hidden=0, keeping=1
08-30 10:07:30.023   433   451 I HOGE    : [23] com.google.tv.launcher(pid=547, uid=10014)
08-30 10:07:30.023   433   451 I HOGE    :         curAdj=0, adjType=top-activity, hidden=0, keeping=1
08-30 10:07:30.023   433   451 I HOGE    : [22] com.google.tv.chrome(pid=1006, uid=1098)
08-30 10:07:30.023   433   451 I HOGE    :         curAdj=0, adjType=broadcast, hidden=0, keeping=1

LowMemoryKiller の too many background によるプロセス kill の仕組み"

ActivityManagerService.java の中の updateOomAdjLocked() は Android の動作中に頻繁に呼び出され、バックグラウンドで走っているプロセスが多くなるとプロセスを自動的に kill するようになっています。

  • hidden 状態のプロセスが MAX_HIDDEN_APPS (15) より多くなると、プロセスが kill される。
  • プロセスの adj 値が computeOomAdjLocked() によって計算され、HIDDEN_APP_MIN_ADJ (7) 以上の値になったプロセスが kill 候補になる(adj >= 7 になる場合はそのプロセスは hidden 状態のはず)。
  • プロセスの使用履歴上で、古いものから順番に kill の対象になる。

引数なしの updateOomAdjLocked() の最後の方で、numHidden の値をダンプするようにすれば、その瞬間にいくつの hidden プロセスが存在するか調べられます。 基本的には動的に計算される adj の値は 0~15 の範囲になりますが、アプリケーションのフラグとして SYSTEMPERSISTENT が設定されている場合は、CORE_SERVER_ADJ (-12) が採用されるようです。

final ProcessRecord appAppLocked(ApplicationInfo info) {
    ...
    if ((info.flags & (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT))
            == (ApplicationInfo.FLAG_SYSTEM | ApplicationInfo.FLAG_PERSISTENT)) {
        app.persistent = true;
        app.maxAdj = CORE_SERVER_ADJ;  // ★
    }
    ...
}

絶対に kill されてはいけない電源管理系のサービスなどは、このフラグを設定しておかなければいけません。 ApplicationInfo.FLAG_SYSTEM の方は、プリインストールアプリ (/system/app/*) の場合は自動で付加され、ApplicationInfo.FLAG_PERSISTENT の方は、AndroidManifest.xmlapplication 要素で以下のように設定するようです。

AndroidManifest.xml
...
<application ... android:persistent="true">
...