Android依託Java型虛擬機,OOM是常常遇到的問題,那麼在快達到OOM的時候,系統難道不能回收部分界面來達到縮減開支的目的碼?在系統內存不足的狀況下,能夠經過AMS及LowMemoryKiller殺優先級低的進程,來回收進程資源。可是這點對於前臺OOM問題並無多大幫助,由於每一個Android應用有一個Java內存上限,好比256或者512M,而系統內存可能有6G或者8G,也就是說,一個APP的進程達到OOM的時候,可能系統內存仍是很充足的,這個時候,系統如何避免OOM的呢?ios是會將不可見界面都回收,以後再恢復,Android作的並無那麼完全,簡單說:對於單棧(TaskRecord)應用,在前臺的時候,全部界面都不會被回收,只有多棧狀況下,系統纔會回收不可見棧的Activity。注意回收的目標是不可見**棧(TaskRecord)**的Activity。java
如上圖,在前臺時,左邊單棧APP跟進程生命週期綁定,多棧的,不可見棧TaskRecord1是有被幹掉風險,TaskRecord2不會。下面簡單分析下。ios
Google應該也是想到了這種狀況,源碼自身就給APP自身回收內存留有入口,在每一個進程啓動的時候,回同步啓動個微小的內存監測工具,入口是ActivityThread的attach函數,Android應用進程啓動後,都會調用該函數:緩存
ActivityThreadapp
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
if (!system) {
...
final IActivityManager mgr = ActivityManagerNative.getDefault();
...
// Watch for getting close to heap limit.
<!--關鍵點1,添加監測工具-->
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();
<!--關鍵點2 :若是已經可用的內存不足1/4着手處理殺死Activity,而且這個時候,沒有緩存進程-->
if (dalvikUsed > ((3*dalvikMax)/4)) {
mSomeActivitiesChanged = false;
try {
mgr.releaseSomeActivities(mAppThread);
} catch (RemoteException e) {
...
}
複製代碼
先關鍵點1,對於非系統進程,經過BinderInternal.addGcWatcher添加了一個內存監測工具,後面會發現,這個工具的檢測時機是每一個GC節點。而對於咱們上文說的回收不可見Task的時機是在關鍵點2:Java使用內存超過3/4的時候,調用AMS的releaseSomeActivities,嘗試釋放不可見Activity,固然,並不是全部不可見的Activity會被回收,當APP內存超過3/4的時候,調用棧以下:ide
以前說過,經過BinderInternal.addGcWatcher就添加了一個內存監測工具,原理是什麼?其實很簡單,就是利用了Java的finalize那一套:JVM垃圾回收器準備釋放內存前,會先調用該對象finalize(若是有的話)。函數
public class BinderInternal {
<!--關鍵點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);
}
<!--關鍵點2 執行以前添加的回調-->
for (int i=0; i<sTmpWatchers.length; i++) {
if (sTmpWatchers[i] != null) {
sTmpWatchers[i].run();
}
}
<!--關鍵點3 下一次輪迴-->
sGcWatcher = new WeakReference<GcWatcher>(new GcWatcher());
}
}
public static void addGcWatcher(Runnable watcher) {
synchronized (sGcWatchers) {
sGcWatchers.add(watcher);
}
}
...
}
複製代碼
這裏有幾個關鍵點,關鍵點1是弱引用,GC的sGcWatcher引用的對象是要被回收的,這樣回收前就會走關鍵點2,遍歷執行以前經過BinderInternal.addGcWatcher添加的回調,執行完畢後,從新爲sGcWatcher賦值新的弱引用,這樣就會走下一個輪迴,這就是爲何GC的時候,有機會觸發releaseSomeActivities,其實,這裏是個不錯的內存監測點,用來擴展自身的需求。工具
若是GC的時候,APP的Java內存使用超過了3/4,就會觸發AMS的releaseSomeActivities,嘗試回收界面,增長可用內存,可是並不是全部場景都會真的銷燬Activity,好比單棧的APP就不會銷燬,多棧的也要分場景,可能選擇性銷燬不可見Activity。this
ActivityManagerServicespa
@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);
<!--若是已經有一個進行,則再也不繼續-->
if (r.finishing || r.state == DESTROYING || r.state == DESTROYED) {
return;
}
<!--過濾-->
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;
<!--關鍵點1 只要要多餘一個TaskRecord纔有機會走這一步,-->
} 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;
}
}
}
}
複製代碼
這裏先看第一個關鍵點1:若是想要tasks非空,則至少須要兩個TaskRecord才行,否則,只有一個firstTask,永遠沒法知足firstTask != r.task這個條件,也沒法走3d
tasks = new ArraySet<>();
複製代碼
也就是說,APP當前進程中,至少兩個TaskRecord纔有必要走Activity的銷燬邏輯,註釋說明很清楚:Didn't find two or more tasks to release,若是能找到超過兩個會怎麼樣呢?
final int releaseSomeActivitiesLocked(ProcessRecord app, ArraySet<TaskRecord> tasks,
String reason) {
<!--maxTasks 保證最多清理- tasks.size() / 4有效個,最少清理一個 同時最少保留一個前臺TaskRecord->
int maxTasks = tasks.size() / 4;
if (maxTasks < 1) {
<!--至少清理一個-->
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個TaskRecord,同時,至少要保證保留一個前臺可見TaskRecord,好比若是有兩個TaskRecord,則清理先前的一個,保留前臺顯示的這個,若是三個,則還要看看最老的是否被有效清理,也就是是否有Activity被清理,若是有則只清理一個,保留兩個,若是沒有,則繼續清理次老的,保留一個前臺展現的,若是有四個,相似,若是有5個,則至少兩個清理,這裏的規則若是有興趣,可本身簡單看下。通常APP中,不多有超過兩個TaskRecord的。
模擬了兩個Task的模型,先啓動在一個棧裏面啓動多個Activity,而後在經過startActivity啓動一個新TaskRecord,而且在新棧中不斷分配java內存,當Java內存使用超過3/4的時候,就會看到前一個TaskRecord棧內Activity被銷燬的Log,同時若是經過studio的layoutinspect查看,會發現APP只保留了新棧內的Activity,驗證了以前的分析。
做者:看書的小蝸牛
Android可見APP的不可見任務棧(TaskRecord)被銷燬分析
僅供參考,歡迎指正