blog

AndroidのLowMemoryKillerメカニズム

Androidのメカニズムは、現在のシステムメモリのしきい値とプロセスの優先度に基づいて、どのプロセスを強制終了するかを決定するメモリ再利用メカニズムです。定期的に現在のシステムの利用可能なメモリをチ...

Dec 2, 2020 · 11 min. read
シェア

LowMemoryKiller

AndroidのLowMemoryKiller(lmk)メカニズムは、現在のシステムメモリのしきい値とプロセスの優先度に基づいて、どのプロセスをkillするかを決定するメモリ再利用メカニズムです。LowMemoryKillerは、利用可能な現在のシステムメモリを定期的にチェックし、システムで利用可能な残りのメモリが少なくなったときにプロセスをkillするようにプロセスをトリガします。異なるメモリ閾値とプロセスの優先度に基づいて、どのプロセスを kill するかを決定します。

AndroidのLowMemoryKiller機構は、LinuxのOOM Killer機構に基づいて変更され、KKバージョンでは、LMK機構と戦略は、LowMemeoryKillerのLバージョンでは、変更するためにいくつかの調整を行い、カーネル空間のドライバに実装されていますが、全体的なアイデアはまだ同じです。

Kernelレイヤーの実装

// kernel/drivers/staging/android/lowmemorykiller.c
static uint32_t lowmem_debug_level = 2;
//低メモリ閾値は優先度に対応し、値が低いほど優先度が高くなる。
static int lowmem_adj[6] = {
	0,
	1,
	6,
	12,
};
static int lowmem_adj_size = 4;
//低メモリしきい値
static size_t lowmem_minfree[6] = {
	3 * 512,	/* 6MB */
	2 * 1024,	/* 8MB */
	4 * 1024,	/* 16MB */
	,	/* 64MB */
};
//システムで利用可能なメモリがある低メモリしきい値より少ない場合、lowmemoryKillerは対応するプロセスより優先順位の低いプロセスに行く。,
//例えば、使用可能なシステム・メモリーが64MB未満の場合、優先順位が12未満のプロセスは強制終了される。
static int lowmem_minfree_size = 4;
 

lowmemorykillerにはlowmem_infreeとlowmem_adjという2つの配列が定義されており、システムの現在の低メモリしきい値と、低メモリしきい値に対応するプロセスの優先度を記述するために使用されます。

static int __init lowmem_init(void)
{
	task_free_register(&task_nb);
	register_shrinker(&lowmem_shrinker);
	return 0;
}
static void __exit lowmem_exit(void)
{
	unregister_shrinker(&lowmem_shrinker);
	task_free_unregister(&task_nb);
}
module_init(lowmem_init);
module_exit(lowmem_exit);

lmkは独自のlowmem_shrinkメソッドをシステムのメモリ検出モジュールに組み込んでおり、メモリ不足時にコールバックすることができます。また、register_shrinker関数は別のメモリ管理モジュールに属する関数です。

//メモリー検出モジュールがメモリー不足を検出したときに呼び出されるこのメソッドは、優先順位の低いプロセスを探す。
static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc)
{
	struct task_struct *p;
	struct task_struct *selected = NULL;
	int rem = 0;
	int tasksize;
	int i;
	int min_adj = OOM_ADJUST_MAX + 1;//最も低い優先度に初期化する
	int selected_tasksize = 0;
	int selected_oom_adj;
	int array_size = ARRAY_SIZE(lowmem_adj);
	//1. 残りのメモリサイズを取得する
	int other_free = global_page_state(NR_FREE_PAGES);
	int other_file = global_page_state(NR_FILE_PAGES) -
						global_page_state(NR_SHMEM);
	
	if (lowmem_deathpending &&
	 time_before_eq(jiffies, lowmem_deathpending_timeout))
		return 0;
	//2つの配列のサイズを変更する
	if (lowmem_adj_size < array_size)
		array_size = lowmem_adj_size;//4
	if (lowmem_minfree_size < array_size)
		array_size = lowmem_minfree_size;//4
	//2. 現在のメモリしきい値を見つける
	for (i = 0; i < array_size; i++) {
		if (other_free < lowmem_minfree[i] &&
		 other_file < lowmem_minfree[i]) {
			min_adj = lowmem_adj[i];//最小メモリしきい値に対応する優先度を見つける。
			break;
		}
	}
	if (sc->nr_to_scan > 0)
		lowmem_print(3, "lowmem_shrink %lu, %x, ofree %d %d, ma %d
",
			 sc->nr_to_scan, sc->gfp_mask, other_free, other_file,
			 min_adj);
	rem = global_page_state(NR_ACTIVE_ANON) +
		global_page_state(NR_ACTIVE_FILE) +
		global_page_state(NR_INACTIVE_ANON) +
		global_page_state(NR_INACTIVE_FILE);
	if (sc->nr_to_scan <= 0 || min_adj == OOM_ADJUST_MAX + 1) {
		lowmem_print(5, "lowmem_shrink %lu, %x, return %d
",
			 sc->nr_to_scan, sc->gfp_mask, rem);
		return rem;
	}
	//最小_adjつまり、優先度を計算し、その優先度以下のプロセスは強制終了される。
	selected_oom_adj = min_adj;
	//3. 優先順位がこの閾値より低いプロセスを見つけ、終了させる。
	read_lock(&tasklist_lock);
	for_each_process(p) {
		struct mm_struct *mm;
		struct signal_struct *sig;
		int oom_adj;
		task_lock(p);
		mm = p->mm;
		sig = p->signal;
		if (!mm || !sig) {
			task_unlock(p);
			continue;
		}
		oom_adj = sig->oom_adj;
		if (oom_adj < min_adj) {
			task_unlock(p);
			continue;
		}
		tasksize = get_mm_rss(mm);
		task_unlock(p);
		if (tasksize <= 0)
			continue;
		if (selected) {
			if (oom_adj < selected_oom_adj)//優先順位が選択された_oom_adjr.appのプロセスは処理されない。
				continue;
			if (oom_adj == selected_oom_adj &&
			 tasksize <= selected_tasksize)
				continue;
		}
		selected = p;
		selected_tasksize = tasksize;
		selected_oom_adj = oom_adj;
		lowmem_print(2, "select %d (%s), adj %d, size %d, to kill
",
			 p->pid, p->comm, oom_adj, tasksize);
	}
	if (selected) {
		lowmem_print(1, "send sigkill to %d (%s), adj %d, size %d
",
			 selected->pid, selected->comm,
			 selected_oom_adj, selected_tasksize);
		lowmem_deathpending = selected;
		lowmem_deathpending_timeout = jiffies + HZ;
		force_sig(SIGKILL, selected);//プロセスを終了する
		rem -= selected_tasksize;
	}
	lowmem_print(4, "lowmem_shrink %lu, %x, return %d
",
		 sc->nr_to_scan, sc->gfp_mask, rem);
	read_unlock(&tasklist_lock);
	return rem;
}

lowmem_shrinkはlmkのコア処理メソッドで、そのロジックは比較的単純で、まずglobal_page_stateから現在のシステムで利用可能な空きメモリサイズを取得し、空きメモリサイズからプロセスの優先度に対応するメモリ閾値を見つけ、selected_oom_adjに割り当てます。for_each_process はプロセスのリストを走査し、 優先度が最も低いプロセスを見つけ、fore_sig によって kill されるプロセスを選択します。

アプリケーション回復メカニズム

AndroidアプリのリカバリーはAMSを通じて行われます。では、AMSのリカバリープロセスはどのように行われ、AMSはどのようにしてアプリが終了したことを知るのでしょうか?

LowMemoryKillerはアプリケーションを強制終了してもアプリケーション層やAMSには通知しませんが、AMSとアプリケーション側の通信はBinderの仕組みに基づいていることが知られており、Binderはblah blah blah関数によって動いていること、Binder ClientはlinkToDeathを介してblah blah blahを受け取るように登録することを忘れてはいけません。Binder Clientは、linkToDeathを通じてblah blahを受け取るように登録します。 Binder Server側が死ぬと、それを通じてクライアントに通知します。ここで、Binder ServerはIApplicationThreadであり、このオブジェクトはActivityThread内に生成され、AMSとクライアントの通信に使用されます。

//アプリケーション作成後、AMSにIApplication Threadをアタッチする。
@Override
public final void attachApplication(IApplicationThread thread) {
 synchronized (this) {
 int callingPid = Binder.getCallingPid();
 final long origId = Binder.clearCallingIdentity();
 attachApplicationLocked(thread, callingPid);
 Binder.restoreCallingIdentity(origId);
 }
}
private final boolean attachApplicationLocked(IApplicationThread thread,
 int pid) {
 ...
 try {
 AppDeathRecipient adr = new AppDeathRecipient(
 app, pid, thread);
 thread.asBinder().linkToDeath(adr, 0);//アプリケーション側のバインダーは、アプリケーションが異常終了したときにAMSが次のことを知ることができるようにする。
 app.deathRecipient = adr;
 } catch (RemoteException e) {
 app.resetPackageList(mProcessStats);
 startProcessLocked(app, "link fail", processName);
 return false;
 }
 ...
}
//アプリケーション・デス・ポルフィリン・レシーバー これはbinderで実装されており、クライアントが異常終了するとbinderサーバーはポルフィリンを受け取る。
private final class AppDeathRecipient implements IBinder.DeathRecipient {
 final ProcessRecord mApp;
 final int mPid;
 final IApplicationThread mAppThread;//これはクライアント側のバインダー・エンティティであり、アプリケーションが強制終了されると、バインダー・ドライバはバインダー・エンティティのブラレポートを送信する。
 AppDeathRecipient(ProcessRecord app, int pid,
 IApplicationThread thread) {
 if (localLOGV) Slog.v(
 TAG, "New death recipient " + this
 + " for thread " + thread.asBinder());
 mApp = app;
 mPid = pid;
 mAppThread = thread;
 }
 //ポルフィリアのコールバック
 @Override
 public void binderDied() {
 if (localLOGV) Slog.v(
 TAG, "Death received in " + this
 + " for thread " + mAppThread.asBinder());
 synchronized(ActivityManagerService.this) {
 appDiedLocked(mApp, mPid, mAppThread);//アプリケーションプロセスの終了をAMSに通知する
 }
 }
}

アプリケーション作成後、AMSはattachApplication経由でattachApplicationLockedを呼び出し、Binderサービスのblahコールバックを登録します。これはIBinder.DeathRecipientを継承したAppDeathRecipientで、サービス側、つまりアプリケーション側がkillされるとbinderDiedを通してAMSに通知し、AMSはappDiedLockedメソッドを通してkillされたアプリケーションのロジックを処理します。この時、AMSがどのように処理するか見てみましょう。

//appアクティビティがバックグラウンドで強制終了されると、AMSはこのメソッドで通知される。
final void appDiedLocked(ProcessRecord app, int pid,
 IApplicationThread thread) {
 ...
 // Clean up already done if the process has been re-started.
 if (app.pid == pid && app.thread != null &&
 app.thread.asBinder() == thread.asBinder()) {
 boolean doLowMem = app.instrumentationClass == null;
 boolean doOomAdj = doLowMem;
 if (!app.killedByAm) {
 mAllowLowerMemLevel = true;
 if("android.process.media".equals(app.processName))
 {
 SystemProperties.set("service.media_oncekilled", "true");
 }
 } else {
 mAllowLowerMemLevel = false;
 doLowMem = false;
 }
 handleAppDiedLocked(app, false, true);//アプリが死んだ場合の処理
 if (doOomAdj) {
 updateOomAdjLocked();
 }
 if (doLowMem) {//メモリ不足の問題を他のアプリに通知するために、AMSはメモリ不足の問題を他のアプリに通知する。
 doLowMemReportIfNeededLocked(app);
 }
 }
 ...
}

これはさらに、appDiedLockedでhandleAppDiedLockedによって処理されます。

private final void handleAppDiedLocked(ProcessRecord app,
 boolean restarting, boolean allowRestart) {
 //アプリケーションに関する情報を消去する
 cleanUpApplicationRecordLocked(app, restarting, allowRestart, -1);
 if (!restarting) {
 removeLruProcessLocked(app);
 }
 if (mProfileProc == app) {
 clearProfilerLocked();
 }
 // Remove this application's activities from active lists.
 boolean hasVisibleActivities = mStackSupervisor.handleAppDiedLocked(app);
 app.activities.clear();
 if (app.instrumentationClass != null) {
 Slog.w(TAG, "Crash of app " + app.processName
 + " running instrumentation " + app.instrumentationClass);
 Bundle info = new Bundle();
 info.putString("shortMsg", "Process crashed.");
 finishInstrumentationLocked(app, Activity.RESULT_CANCELED, info);
 }
 if (!restarting) {
 if (!mStackSupervisor.resumeTopActivitiesLocked()) {
 if (hasVisibleActivities) {
 mStackSupervisor.ensureActivitiesVisibleLocked(null, 0);
 }
 }
 }
}

handleAppDiedLockedは主に以下のことを行います:

private final void cleanUpApplicationRecordLocked(ProcessRecord app,
 boolean restarting, boolean allowRestart, int index) {
 ...
 mServices.killServicesLocked(app, allowRestart);
 
 boolean restart = false;
 //公開されたプロボイダーをクリーンアップする
 // Remove published content providers.
 for (int i=app.pubProviders.size()-1; i>=0; i--) {
 ContentProviderRecord cpr = app.pubProviders.valueAt(i);
 final boolean always = app.bad || !allowRestart;
 if (removeDyingProviderLocked(app, cpr, always) || always) {
 // We left the provider in the launching list, need to
 // restart it.
 restart = true;
 }
 cpr.provider = null;
 cpr.proc = null;
 }
 app.pubProviders.clear();
 // Take care of any launching providers waiting for this process.
 if (checkAppInLaunchingProvidersLocked(app, false)) {
 restart = true;
 }
 
 // Unregister from connected content providers.
 if (!app.conProviders.isEmpty()) {
 for (int i=0; i<app.conProviders.size(); i++) {
 ContentProviderConnection conn = app.conProviders.get(i);
 conn.provider.connections.remove(conn);
 }
 app.conProviders.clear();
 }
 ...
 skipCurrentReceiverLocked(app);
 //すべてのレシーバーの登録を解除する
 // Unregister any receivers.
 for (int i=app.receivers.size()-1; i>=0; i--) {
 removeReceiverLocked(app.receivers.valueAt(i));
 }
 app.receivers.clear();
 ...
 if (restart && !app.isolated) {
 // We have components that still need to be running in the
 // process, so re-launch it.
 mProcessNames.put(app.processName, app.uid, app);
 startProcessLocked(app, "restart", app.processName);
 } 
 ...
}
boolean handleAppDiedLocked(ProcessRecord app) {
 boolean hasVisibleActivities = false;
 for (int stackNdx = mStacks.size() - 1; stackNdx >= 0; --stackNdx) {
 hasVisibleActivities |= mStacks.get(stackNdx).handleAppDiedLocked(app);
 }
 return hasVisibleActivities;
}
boolean handleAppDiedLocked(ProcessRecord app) {
 if (mPausingActivity != null && mPausingActivity.app == app) {
 mPausingActivity = null;
 }
 if (mLastPausedActivity != null && mLastPausedActivity.app == app) {
 mLastPausedActivity = null;
 mLastNoHistoryActivity = null;
 }
 return removeHistoryRecordsForAppLocked(app);
}
boolean removeHistoryRecordsForAppLocked(ProcessRecord app) {
 removeHistoryRecordsForAppLocked(mLRUActivities, app, "mLRUActivities");
 removeHistoryRecordsForAppLocked(mStackSupervisor.mStoppingActivities, app,
 "mStoppingActivities");
 removeHistoryRecordsForAppLocked(mStackSupervisor.mGoingToSleepActivities, app,
 "mGoingToSleepActivities");
 removeHistoryRecordsForAppLocked(mStackSupervisor.mWaitingVisibleActivities, app,
 "mWaitingVisibleActivities");
 removeHistoryRecordsForAppLocked(mStackSupervisor.mFinishingActivities, app,
 "mFinishingActivities");
 // Clean out the history list.
 int i = numActivities();
 for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
 final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
 for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
 final ActivityRecord r = activities.get(activityNdx);
 --i;
 if (r.app == app) {
 boolean remove;
 if ((!r.haveState && !r.stateNotNeeded) || r.finishing) {
 // Don't currently have state for the activity, or
 // it is finishing -- always remove it.
 remove = true;
 } else if (r.launchCount > 2 &&
 r.lastLaunchTime > (SystemClock.uptimeMillis()-60000)) {
 // We have launched this activity too many times since it was
 // able to run, so give up and remove it.
 remove = true;
 } else {
 // The process may be gone, but the activity lives on!
 remove = false;
 }
 if (remove) {
 if (!r.finishing) {
 if (r.state == ActivityState.RESUMED) {
 mService.updateUsageStats(r, false);
 }
 }
 removeActivityFromHistoryLocked(r);
 }else{
 if (r.visible) {
 hasVisibleActivities = true;
 }
 r.app = null;//appをnullに設定する
 r.nowVisible = false;
 if (!r.haveState) {
 r.icicle = null;
 }
 }
 cleanUpActivityLocked(r, true, true);
 }
 }
 }
}
final boolean resumeTopActivityLocked(ActivityRecord prev, Bundle options) {
 ...
 if (next.app != null && next.app.thread != null) {
 }else {
 //1.開始したいアクティビティは、まだプロセスを作成していない。
 //2.appまたは、アクティビティが異常終了したため、再起動する必要がある。
 // Whoops, need to restart this activity!
 if (!next.hasBeenLaunched) {
 next.hasBeenLaunched = true;
 } 
 mStackSupervisor.startSpecificActivityLocked(next, true, true);
 }
}
void startSpecificActivityLocked(ActivityRecord r,
 boolean andResume, boolean checkConfig) {
 if (app != null && app.thread != null) {
 ...
 }
 ...
 mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0,
 "activity", r.intent.getComponent(), false, false, true);
}

r.appがnullに設定された後、AMSはアクティビティの対応するプロセスを再構築します。

Read next

ユーザーガイドコンポーネントのvue実装

初めて要望を受けた時、こんな汎用的なものなら誰かが既に作っているはずだと思い、急いでgayhubに見に行くと、確かにありました - vue-tour。 公式エフェクトを見た後、バブルコンポーネントのように見せる方法、エフェクトとapiは全て中庸です。 しかし、私たちのUIは、カスタムコンテンツに従事し、より軽薄であり、ギャップがかなり大きく、停止したり、自分自身を jerk する必要があります。 私は、画面上のマスクレイヤーを強調表示したい...

Dec 1, 2020 · 7 min read