Android依托Java型虛擬機(jī),OOM是經(jīng)常遇到的問(wèn)題础倍,那么在快達(dá)到OOM的時(shí)候烛占,系統(tǒng)難道不能回收部分界面來(lái)達(dá)到縮減開支的目的碼?在系統(tǒng)內(nèi)存不足的情況下沟启,可以通過(guò)AMS及LowMemoryKiller殺優(yōu)先級(jí)低的進(jìn)程忆家,來(lái)回收進(jìn)程資源。但是這點(diǎn)對(duì)于前臺(tái)OOM問(wèn)題并沒有多大幫助德迹,因?yàn)槊總€(gè)Android應(yīng)用有一個(gè)Java內(nèi)存上限芽卿,比如256或者512M,而系統(tǒng)內(nèi)存可能有6G或者8G胳搞,也就是說(shuō)卸例,一個(gè)APP的進(jìn)程達(dá)到OOM的時(shí)候,可能系統(tǒng)內(nèi)存還是很充足的肌毅,這個(gè)時(shí)候筷转,系統(tǒng)如何避免OOM的呢?ios是會(huì)將不可見界面都回收芽腾,之后再恢復(fù)旦装,Android做的并沒有那么徹底,簡(jiǎn)單說(shuō):對(duì)于單棧(TaskRecord)應(yīng)用摊滔,在前臺(tái)的時(shí)候阴绢,所有界面都不會(huì)被回收,只有多棧情況下艰躺,系統(tǒng)才會(huì)回收不可見棧的Activity呻袭。注意回收的目標(biāo)是不可見棧(TaskRecord)的Activity。
前臺(tái)APP回收?qǐng)鼍?/p>
如上圖腺兴,在前臺(tái)時(shí)左电,左邊單棧APP跟進(jìn)程生命周期綁定,多棧的页响,不可見棧TaskRecord1是有被干掉風(fēng)險(xiǎn)篓足,TaskRecord2不會(huì)。下面簡(jiǎn)單分析下闰蚕。
Android原生提供內(nèi)存回收入口
Google應(yīng)該也是想到了這種情況栈拖,源碼自身就給APP自身回收內(nèi)存留有入口,在每個(gè)進(jìn)程啟動(dòng)的時(shí)候没陡,回同步啟動(dòng)個(gè)微小的內(nèi)存監(jiān)測(cè)工具涩哟,入口是ActivityThread的attach函數(shù)索赏,Android應(yīng)用進(jìn)程啟動(dòng)后,都會(huì)調(diào)用該函數(shù):
ActivityThread
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
...
final IActivityManager mgr = ActivityManagerNative.getDefault();
...
// Watch for getting close to heap limit.
<!--關(guān)鍵點(diǎn)1贴彼,添加監(jiān)測(cè)工具-->
BinderInternal.addGcWatcher(new Runnable() {
@Override public void run() {
if (!mSomeActivitiesChanged) {
return;
}
Runtime runtime = Runtime.getRuntime();
long dalvikMax = runtime.maxMemory();
long dalvikUsed = runtime.totalMemory() - runtime.freeMemory();
<!--關(guān)鍵點(diǎn)2 :如果已經(jīng)可用的內(nèi)存不足1/4著手處理殺死Activity潜腻,并且這個(gè)時(shí)候,沒有緩存進(jìn)程-->
if (dalvikUsed > ((3*dalvikMax)/4)) {
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
...
}
先關(guān)鍵點(diǎn)1器仗,對(duì)于非系統(tǒng)進(jìn)程融涣,通過(guò)BinderInternal.addGcWatcher添加了一個(gè)內(nèi)存監(jiān)測(cè)工具,后面會(huì)發(fā)現(xiàn)精钮,這個(gè)工具的檢測(cè)時(shí)機(jī)是每個(gè)GC節(jié)點(diǎn)暴心。而對(duì)于我們上文說(shuō)的回收不可見Task的時(shí)機(jī)是在關(guān)鍵點(diǎn)2:Java使用內(nèi)存超過(guò)3/4的時(shí)候,調(diào)用AMS的releaseSomeActivities杂拨,嘗試釋放不可見Activity,當(dāng)然悯衬,并非所有不可見的Activity會(huì)被回收弹沽,當(dāng)APP內(nèi)存超過(guò)3/4的時(shí)候,調(diào)用棧如下:
APP內(nèi)存超過(guò)3/4就會(huì)嘗試GC
APP在GC節(jié)點(diǎn)的內(nèi)存監(jiān)測(cè)機(jī)制
之前說(shuō)過(guò)筋粗,通過(guò)BinderInternal.addGcWatcher就添加了一個(gè)內(nèi)存監(jiān)測(cè)工具策橘,原理是什么?其實(shí)很簡(jiǎn)單娜亿,就是利用了Java的finalize那一套:JVM垃圾回收器準(zhǔn)備釋放內(nèi)存前丽已,會(huì)先調(diào)用該對(duì)象finalize(如果有的話)。
public class BinderInternal {
<!--關(guān)鍵點(diǎn)1 弱引用-->
static WeakReference<GcWatcher> sGcWatcher
= new WeakReference<GcWatcher>(new GcWatcher());
static ArrayList<Runnable> sGcWatchers = new ArrayList<>();
static Runnable[] sTmpWatchers = new Runnable[1];
static long sLastGcTime;
static final class GcWatcher {
@Override
protected void finalize() throws Throwable {
handleGc();
sLastGcTime = SystemClock.uptimeMillis();
synchronized (sGcWatchers) {
sTmpWatchers = sGcWatchers.toArray(sTmpWatchers);
}
<!--關(guān)鍵點(diǎn)2 執(zhí)行之前添加的回調(diào)-->
for (int i=0; i<sTmpWatchers.length; i++) {
if (sTmpWatchers[i] != null) {
sTmpWatchers[i].run();
}
}
<!--關(guān)鍵點(diǎn)3 下一次輪回-->
sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
}
}
public static void addGcWatcher(Runnable watcher) {
synchronized (sGcWatchers) {
sGcWatchers.add(watcher);
}
}
...
}
這里有幾個(gè)關(guān)鍵點(diǎn)买决,關(guān)鍵點(diǎn)1是弱引用沛婴,GC的sGcWatcher引用的對(duì)象是要被回收的,這樣回收前就會(huì)走關(guān)鍵點(diǎn)2督赤,遍歷執(zhí)行之前通過(guò)BinderInternal.addGcWatcher添加的回調(diào)嘁灯,執(zhí)行完畢后,重新為sGcWatcher賦值新的弱引用躲舌,這樣就會(huì)走下一個(gè)輪回丑婿,這就是為什么GC的時(shí)候,有機(jī)會(huì)觸發(fā)releaseSomeActivities没卸,其實(shí)羹奉,這里是個(gè)不錯(cuò)的內(nèi)存監(jiān)測(cè)點(diǎn),用來(lái)擴(kuò)展自身的需求约计。
AMS的TaskRecord棧釋放機(jī)制
如果GC的時(shí)候诀拭,APP的Java內(nèi)存使用超過(guò)了3/4,就會(huì)觸發(fā)AMS的releaseSomeActivities病蛉,嘗試回收界面炫加,增加可用內(nèi)存瑰煎,但是并非所有場(chǎng)景都會(huì)真的銷毀Activity,比如單棧的APP就不會(huì)銷毀俗孝,多棧的也要分場(chǎng)景酒甸,可能選擇性銷毀不可見Activity。
ActivityManagerService
@Override
public void releaseSomeActivities(IApplicationThread appInt) {
synchronized(this) {
final long origId = Binder.clearCallingIdentity();
try {
ProcessRecord app = getRecordForAppLocked(appInt);
mStackSupervisor.releaseSomeActivitiesLocked(app, "low-mem");
} finally {
Binder.restoreCallingIdentity(origId);
}
}
}
void releaseSomeActivitiesLocked(ProcessRecord app, String reason) {
TaskRecord firstTask = null;
ArraySet<TaskRecord> tasks = null;
for (int i = 0; i < app.activities.size(); i++) {
ActivityRecord r = app.activities.get(i);
<!--如果已經(jīng)有一個(gè)進(jìn)行赋铝,則不再繼續(xù)-->
if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) {
return;
}
<!--過(guò)濾-->
if (r.visible || !r.stopped || !r.haveState || r.state == RESUMED || r.state == PAUSING
|| r.state == PAUSED || r.state == STOPPING) {
continue;
}
if (r.task != null) {
if (firstTask == null) {
firstTask = r.task;
<!--關(guān)鍵點(diǎn)1 只要要多余一個(gè)TaskRecord才有機(jī)會(huì)走這一步插勤,-->
} else if (firstTask != r.task) {
if (tasks == null) {
tasks = new ArraySet<>();
tasks.add(firstTask);
}
tasks.add(r.task);
}
}
}
<!--注釋很明顯,-->
if (tasks == null) {
if (DEBUG_RELEASE) Slog.d(TAG_RELEASE, "Didn't find two or more tasks to release");
return;
}
// If we have activities in multiple tasks that are in a position to be destroyed,
// let's iterate through the tasks and release the oldest one.
final int numDisplays = mActivityDisplays.size();
for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
final ArrayList<ActivityStack> stacks = mActivityDisplays.valueAt(displayNdx).mStacks;
// Step through all stacks starting from behind, to hit the oldest things first.
for (int stackNdx = 0; stackNdx < stacks.size(); stackNdx++) {
final ActivityStack stack = stacks.get(stackNdx);
// Try to release activities in this stack; if we manage to, we are done.
if (stack.releaseSomeActivitiesLocked(app, tasks, reason) > 0) {
return;
}
}
}
}
這里先看第一個(gè)關(guān)鍵點(diǎn)1:如果想要tasks非空革骨,則至少需要兩個(gè)TaskRecord才行农尖,不然,只有一個(gè)firstTask良哲,永遠(yuǎn)無(wú)法滿足firstTask != r.task這個(gè)條件盛卡,也無(wú)法走
tasks = new ArraySet<>();
也就是說(shuō),APP當(dāng)前進(jìn)程中筑凫,至少兩個(gè)TaskRecord才有必要走Activity的銷毀邏輯滑沧,注釋說(shuō)明很清楚:Didn't find two or more tasks to release,如果能找到超過(guò)兩個(gè)會(huì)怎么樣呢巍实?
final int releaseSomeActivitiesLocked(ProcessRecord app, ArraySet<TaskRecord> tasks,
String reason) {
<!--maxTasks 保證最多清理- tasks.size() / 4有效個(gè)滓技,最少清理一個(gè) 同時(shí)最少保留一個(gè)前臺(tái)TaskRecord->
int maxTasks = tasks.size() / 4;
if (maxTasks < 1) {
<!--至少清理一個(gè)-->
maxTasks = 1;
}
int numReleased = 0;
for (int taskNdx = 0; taskNdx < mTaskHistory.size() && maxTasks > 0; taskNdx++) {
final TaskRecord task = mTaskHistory.get(taskNdx);
if (!tasks.contains(task)) {
continue;
}
int curNum = 0;
final ArrayList<ActivityRecord> activities = task.mActivities;
for (int actNdx = 0; actNdx < activities.size(); actNdx++) {
final ActivityRecord activity = activities.get(actNdx);
if (activity.app == app && activity.isDestroyable()) {
destroyActivityLocked(activity, true, reason);
if (activities.get(actNdx) != activity) {
actNdx--;
}
curNum++;
}
}
if (curNum > 0) {
numReleased += curNum;
maxTasks--;
if (mTaskHistory.get(taskNdx) != task) {
// The entire task got removed, back up so we don't miss the next one.
taskNdx--;
}
}
}
return numReleased;
}
ActivityStack利用maxTasks 保證,最多清理tasks.size() / 4棚潦,最少清理1個(gè)TaskRecord令漂,同時(shí),至少要保證保留一個(gè)前臺(tái)可見TaskRecord丸边,比如如果有兩個(gè)TaskRecord叠必,則清理先前的一個(gè),保留前臺(tái)顯示的這個(gè)妹窖,如果三個(gè)挠唆,則還要看看最老的是否被有效清理,也就是是否有Activity被清理嘱吗,如果有則只清理一個(gè)玄组,保留兩個(gè),如果沒有谒麦,則繼續(xù)清理次老的俄讹,保留一個(gè)前臺(tái)展示的,如果有四個(gè)绕德,類似患膛,如果有5個(gè),則至少兩個(gè)清理耻蛇,這里的規(guī)則如果有興趣踪蹬,可自己簡(jiǎn)單看下胞此。一般APP中,很少有超過(guò)兩個(gè)TaskRecord的跃捣。
demo驗(yàn)證
模擬了兩個(gè)Task的模型漱牵,先啟動(dòng)在一個(gè)棧里面啟動(dòng)多個(gè)Activity,然后在通過(guò)startActivity啟動(dòng)一個(gè)新TaskRecord疚漆,并且在新棧中不斷分配java內(nèi)存酣胀,當(dāng)Java內(nèi)存使用超過(guò)3/4的時(shí)候,就會(huì)看到前一個(gè)TaskRecord棧內(nèi)Activity被銷毀的Log娶聘,同時(shí)如果通過(guò)studio的layoutinspect查看闻镶,會(huì)發(fā)現(xiàn)APP只保留了新棧內(nèi)的Activity,驗(yàn)證了之前的分析丸升。
總結(jié)
- 單棧的進(jìn)程铆农,Activity跟進(jìn)程聲明周期一致
- 多棧的,只有不可見棧的Activity可能被銷毀(Java內(nèi)存超過(guò)3/4,不可見)
- 該回收機(jī)制利用了Java虛擬機(jī)的gc機(jī)finalize
- 至少兩個(gè)TaskRecord占才有效狡耻,所以該機(jī)制并不激進(jìn)顿涣,因?yàn)橹髁鰽PP都是單棧。