内存管理包括两个部分
1.当应用程序关闭后,后台对应的进程并没有真正退出,以便下次启动时能够快速启动
2.当系统内存不够用时,Ams会主动根据一定的规则退出优先级较低的进程
1.关闭而不退出
每个应用程序的主体都对应一个ActivityThread类,该类初始化之后,就进入Looper.loop()函数中无限循环,以后则依靠消息机制来运行,当有消息处理时处理消息,而没有消息时进程会进入到sleep状态。
public static void main(String[] args) {
//looper...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
//handler
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();//进入循环
throw new RuntimeException("Main thread loop unexpectedly exited");
}
loop方法中执行for循环
public static void loop() {
final Looper me = myLooper();
final MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
final long ident = Binder.clearCallingIdentity();
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
msg.target.dispatchMessage(msg);
} finally {
}
msg.recycleUnchecked();
}
}
queue.next方法中,会从消息队列中取出消息,如果没有消息,函数内部会导致当前线程进程进入sleep状态,并且只有新消息到达后next函数才继续执行并返回新的消息。queue.next会在三种情况下被唤醒
1.定时器中断。如果应用程序中设置了定时器任务,那么定时器发生时,操作系统会回调该定时器任务,可以在定时器任务中向looper发送一条消息,从而next方法会被唤醒,并返回得到的消息
2.用户按键消息。当有用户按键消息时,wms中专门处理消息的线程会把这个消息发送到looper中
3.Binder中断。当binder驱动接收到binder消息,并派发到客户端的binder对象后,binder线程开始执行,如果在binder的消息处理中向looper发一条消息,next就会继续执行。
大多数应用程序属于消息驱动模式,没有消息程序就会sleep,知道产生消息
2.android与Linux的配合
系统内存是否够用属于linux内核的内存管理控制的事情,ams是无法预知的。
2.1 为什么ams不能判断内存是否够用
1.应用程序申请内存不会通知Ams,所以Ams无法感知应用程序申请内存的情况,除非每次应用程序发生oom时通知ams,但系统并没有这么做
2.java虚拟机运行时都有各自独立的内存空间,应用程序A发生oom并不意味着应用B也会发生oom,很有可能是A程序用光了自己内存的上限,而系统内存还是有的
单纯AMS是无法感知系统内存是否低的
2.2 结合linux
android底层的linux,由于没有采用磁盘虚拟内存机制,所以应用程序能够使用的内存大小完全取决于设计物理内存的大小,所以内存低的含义就是实际物理内存被用的所剩无几了
TU 10-19
在android中运行了一个OOM进程,该进程启动时首先会想linux内核中把自己注册为一个OOM Killer,即当Linux内核的内存管理模块检测到系统内存低的时候就会通知已经注册的OOM进程,然后OOMKiller就可以根据规则进行内存释放了。
OOMKiller 在运行时,Ams需要把每一个应用程序的oom_adj值告知给Killer,值越低就越重要。
Ams仅仅是根据应用的前后台关系分配给应用一个oom_adj值,剩下的就是OOM Killer的事情了
3.关闭程序
第一种情况:从调用startActivity开始,一般情况下,当前会有正在运行的activity,所以需要暂停当前activity,暂停完毕后,Ams会收到一个Binder消息,并开始从completePaused执行,在该函数中,由于上一个activity没有finishing,仅仅是stop,所以这里会把上一个activity加入到mStoppingActivities中,当目标Activity启动后,会向Ams发送一个请求进行内存回收的消息,这导致Ams内部调用activityIdleInternal方法,该方法会首先处理mStoppingActivities中的Activity对象,这会调用到stopActivityLocked方法,该方法通过IPC调用,通知应用进程stop指定的activity,stop完成后再通知Ams,于是Ams从activityStopped处开始执行,而这会调用trimApplications方法,trimApplications方法中会执行和内存相关的操作
第二种情况:按下Back键,会调用finishActivityLocked,然后把Activity的finishing标志设为true,然后再调用startPausingLocked,当目标actiity完成暂停后,就会通知Ams,此时Ams从completePaused开始执行,由于此时暂停的Activity的finising状态已经变为true,所以会执行finishingActivtyLocked。
第三种情况:当Activity启动后,向Ams发送一个Idle消息,这会导致Ams开始执行activityIdleInternal方法,该方法首先处理mStoppingActivities中的对象,接着处理mFinishingActivities列表,然后再调用trimApplications
这三种情况包括stop和destroy,对应onStop和onDestroy方法
从内存回收角度看,释放内存的地点包括三个
1.Ams中进行,也就是当系统内存低时,优先释放没有任何Activity的进程,然后释放非前台Activity对应的进程
2.第二个是在OOMKiller中,此时Ams要告知OOMKiller各个应用进程的优先级,然后OOMKiller就会调用Linux内部的进程管理方法杀死优先级较低的进程
3.在应用进程本身,当Ams认为目标进程需要被杀死,首先会通知目标进程进行内存释放,包括调用目标进程的scheduleLowMemory和procssInBackground方法。
4.释放内存
4.1 activityIdleInternal
TU 10-12
4.1.1 最常见的调用到该方法的情况是目标activity启动后。
当指定的activity启动后会报告ams,此时上一个activity仅仅处于pause状态,还有stop或destroy,在目标activity的启动时会通过如下代码发送一个idle消息
handleResumeActivity{
......
Looper.myQueue().addIdleHandler(new Idler());
......
}
handleMssage{
......
am.activityIdle(a.token, a.createdConfig, stopProfiling);
......
}
4.1.2 窗口从非显示变成显示windowVisible,会告知ams
窗口显示后如果没有用户操作的话,可以被视为空闲状态,这点和第一种情况很类似。对于一般的Activity而言,resume后要通知ams进行空闲回收,而其他窗口显示出来也要通知ams进行空闲回收,岂不是有点重复?原因是有些窗口仅仅是窗口,不对应任何activity,并且activityIdle方法内部并不仅仅是回收刚刚暂停的activity,而是整个系统内部的状态检查
4.1.3 completePausedLocked方法中
发送IDLE_NOW_MSG
4.1.4 completeResumeLocked
检测启动超时,如果在指定时间内不能启动指定activity,则会调用activityIdleInternal释放相关资源。
4.1.5 activityIdleInternal 方法分析
该方法中并不涉及真正的回收内存的操作,真正回收内存的操作是通过调用trimApplication来实现的
1.通知所有需要回收内存的客户进程进行内存回收
2.取出mStoppingActivities列表中的内容,并存放到临时表stops中,取出mFinishingActivities中的内容放入临时表finishes中,然后删除原有的记录
3.首先对stops表进行处理,处理的过程中有一个判断activity的finishing状态的条件,这个表中的activity一般情况下finishing为false,stopped为true,但是不一定finishing状态全是false,如按下Back键,finishing状态就是true,接收在completePaused中调用到了finishCurrentActivity函数,该函数在把指定的activity放到mStoppingActivities中;在这步处理中如果finishing为false,则调用stopActivityLocked通知客户端stop该activity,如果finish状态为true,则需要判断是否需要立即停止,如果要立即停止,就调用destroyActivityLocked通知目标调用onDestroy,如果不需要立即停止,就先调用resumeTopActivity运行下一个activity
4.对finishes列表中的对象进行处理,由于finishes列表中对象的finishing状态都是true,所以可以直接调用destroyActivityLocked通知客户进程销毁目标activity。
5.调用trimApplication
4.2 trimApplications
有两处调用该方法的地方activityIdleInternal和stopActivityLocked
final void trimApplications() {
synchronized (this) {
int i;
// First remove any unused application processes whose package
// has been removed.
for (i=mRemovedProcesses.size()-1; i>=0; i--) {
final ProcessRecord app = mRemovedProcesses.get(i);
if (app.activities.size() == 0
&& app.curReceiver == null && app.services.size() == 0) {
Slog.i(
TAG, "Exiting empty application process "
+ app.processName + " ("
+ (app.thread != null ? app.thread.asBinder() : null)
+ ")\n");
if (app.pid > 0 && app.pid != MY_PID) {
app.kill("empty", false);
} else {
try {
app.thread.scheduleExit();
} catch (Exception e) {
// Ignore exceptions.
}
}
cleanUpApplicationRecordLocked(app, false, true, -1);
mRemovedProcesses.remove(i);
if (app.persistent) {
addAppLocked(app.info, false, null /* ABI override */);
}
}
}
// Now update the oom adj for all processes.
updateOomAdjLocked();
}
}
1.删除mRemovedProcesses列表中包含的应用进程,该列表的内容来自四种情况;第一种,当某个进程crash后,会被添加进这个表;第二种,某个程序的ui线程在5秒内没有响应,系统弹出一个anr对话框,此时如果用户选择强制关闭,该进程会被添加进这个列表;第三种是当调用ams提供的killBackgroundProcess方法时(调用者需要有KILL_BACKGROUND_PROCESS权限);第四种,当系统启动时,Ams的systemReady方法中,如果发现启动了非persistant类型的进程,则把这些进程加入到表中。
2.调用updateOomAdjLocked,该方法的作用是告诉OOMKiller指定进程的优先级,值越小越重要,该函数的返回值是boolean类型,如果底层linux包含OOMKiller则返回true,否则返回false
3.如果不支持OOM,则执行Ams的规则,也就是优先杀死后台activity等
4.最后,无论是使用oom还是ams的规则,如果杀死后台进程之后,此时运行的activity的数量依然超过MAX_ACTIVITIES(20),则需要继续销毁满足以下三个条件的activity;第一,已经stop还没有finishing的;第二,必须是不可见的,也就是说该activity窗口上面有其他全屏窗口;第三,不能使persistent类型的
trimApplications之后,如果内存依然不够,那就无能为力了
4.3 updateOomAdjLocked
该方法的作用是告诉OOM指定进程的优先级
4.3.1 调用computeOomAdjLocked
先获取当前进程,然后遍历lru表中的数据,对每个数据执行computeOomAjdLocked方法
final void updateOomAdjLocked() {
final ActivityRecord TOP_ACT = resumedAppLocked();
final ProcessRecord TOP_APP = TOP_ACT != null ? TOP_ACT.app : null;
final long now = SystemClock.uptimeMillis();
final long oldTime = now - ProcessList.MAX_EMPTY_TIME;
final int N = mLruProcesses.size();
//......
for (int i=N-1; i>=0; i--) {
ProcessRecord app = mLruProcesses.get(i);
if (!app.killedByAm && app.thread != null) {
app.procStateChanged = false;
computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
4.3.2 computeOomAdjLocked
4.3.2.1 computeOomAdjLocked 关于activity的处理的第一阶段
1.判断该进程是否是TOP_APP,如果是优先级自然最高
2.判断该进程中是否包含instrumentationClass,该值一般在单元测试进程中存在,如果是优先级最高
3.判断该进程是否包含持久的activity对象,如果有优先级最高
4.判断改进陈是否包含正在执行的receiver对象,如果有优先级最高
5.判断当前是否有正在执行的service对象,如果有则优先级最高
这五种情况adj都是FOREGROUND_APP_ADJ,也就是说这五种情况,指定的客户端进程都不能被杀死,其优先级最高而且平等
//computeOomAdjLocked(app, ProcessList.UNKNOWN_ADJ, TOP_APP, true, now);
//app是从lru列表中取出的,TOP_APP是当前正在运行activity所属的app,now是当前时间
private final int computeOomAdjLocked(ProcessRecord app, int cachedAdj, ProcessRecord TOP_APP,
boolean doingAll, long now) {
if (mAdjSeq == app.adjSeq) {
return app.curRawAdj;
}
if (app.thread == null) {
app.adjSeq = mAdjSeq;
app.curSchedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
app.curProcState = ActivityManager.PROCESS_STATE_CACHED_EMPTY;
return (app.curAdj=app.curRawAdj=ProcessList.CACHED_APP_MAX_ADJ);
}
if (app == TOP_APP) {//app就是当前正在运行的activity也就是TOP_APP,
// The last app on the list is the foreground app.//
adj = ProcessList.FOREGROUND_APP_ADJ;//设置为前台进程FOREGROUND_APP_ADJ
schedGroup = ProcessList.SCHED_GROUP_TOP_APP;
app.adjType = "top-activity";
foregroundActivities = true;
procState = PROCESS_STATE_CUR_TOP;
} else if (app.instrumentationClass != null) {//这个一般在单元测试进程中存在
// Don't want to kill running instrumentation.
adj = ProcessList.FOREGROUND_APP_ADJ;//设置为前台进程FOREGROUND_APP_ADJ
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
app.adjType = "instrumentation";//type
procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
} else if ((queue = isReceivingBroadcast(app)) != null) {
//判断app中是否有正在执行的reiceiver对象,如果有也是设置为前台进程
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = (queue == mFgBroadcastQueue)
? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
app.adjType = "broadcast";//adjType为broadcast
procState = ActivityManager.PROCESS_STATE_RECEIVER;
} else if (app.executingServices.size() > 0) {
// An app that is currently executing a service callback also counts as being in the foreground.
// app如果当前正在执行service里的回调方法,也算前台进程
adj = ProcessList.FOREGROUND_APP_ADJ;
schedGroup = app.execServicesFg ?
ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND;
app.adjType = "exec-service";//ajdtype是exec-service
procState = ActivityManager.PROCESS_STATE_SERVICE;
}
4.3.2.2 computeOomAdjLocked 关于activity的处理的第二阶段
1.判断指定的进程是否正在调用前台进程的service对象,如果是的话,则其优先级为VISIBLE_APP_ADJ,这种情况的进程优先级略低于前台进程,但因为它和前台进程正处於互动,所以有VISIBLE_APP_ADJ优先级
2.判断forcingToForeground变量是否为空,如果不为空,说明该进程正在被用户强制调到前台,这种情况是瞬间的,但如果是这种情况,优先级也是比较高的VISIBLE_APP_ADJ
if (adj > ProcessList.PERCEPTIBLE_APP_ADJ
|| procState > ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE) {
if (app.foregroundServices) {
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE;
app.cached = false;
app.adjType = "fg-service";
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
} else if (app.forcingToForeground != null) {
// The user is aware of this app, so make it visible.
adj = ProcessList.PERCEPTIBLE_APP_ADJ;
procState = ActivityManager.PROCESS_STATE_IMPORTANT_FOREGROUND;
app.cached = false;
app.adjType = "force-fg";
app.adjSource = app.forcingToForeground;
schedGroup = ProcessList.SCHED_GROUP_DEFAULT;
}
}
4.3.2.3 computeOomAdjLocked 关于activity的处理的第三阶段
if (app == mHomeProcess) {
if (adj > ProcessList.HOME_APP_ADJ) {
// This process is hosting what we currently consider to be the
// home app, so we don't want to let it go into the background.
adj = ProcessList.HOME_APP_ADJ;
schedGroup = ProcessList.SCHED_GROUP_BACKGROUND;
app.cached = false;
app.adjType = "home";
}
if (procState > ActivityManager.PROCESS_STATE_HOME) {
procState = ActivityManager.PROCESS_STATE_HOME;
}
}
判断是否是Home进程,如果是的话,优先级调整为HOME_APP_ADJ
4.3.2.6 computeOomAdjLocked 关于activity的处理的第六阶段
判断是否为mBackupTarget进程,由于备份进程要在后台持续收集数据,因此,优先级高于一般进程,低于visible进程
if (mBackupTarget != null && app == mBackupTarget.app) {
// If possible we want to avoid killing apps while they're being backed up
if (adj > ProcessList.BACKUP_APP_ADJ) {
if (DEBUG_BACKUP) Slog.v(TAG_BACKUP, "oom BACKUP_APP_ADJ for " + app);
adj = ProcessList.BACKUP_APP_ADJ;
if (procState > ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND) {
procState = ActivityManager.PROCESS_STATE_IMPORTANT_BACKGROUND;
}
app.adjType = "backup";
app.cached = false;
}
if (procState > ActivityManager.PROCESS_STATE_BACKUP) {
procState = ActivityManager.PROCESS_STATE_BACKUP;
}
}
4.3.2.7 computeOomAdjLocked 关于service的处理
1.循环针对每个service的状态调整adj,在每个service对象中判断service是否被启动过,如果为true表示被启动过,并且还没超出MAS_SERVICE_INACTIVITY,则设置优先级为SECONDARY_SERVICE_ADJ
2.处理和该service有连接的客户端,并根据客户端的优先级调整service的优先级,首先判断客户端是否和service处于统一进程,如果是,就不调整
3.判断service是否以BIND_AUTO_CREATE方式启动,如果是说明其重要程度取决于客户端,否则不做任何处理
4.经过一系列调用,结果是,如果当前app没有client重要,则把重要性调整到和client相同重要,否则保持当前的重要性
5.判断客户端是否有对应的activity,并且该activtity是否处于resumed或者pausing状态,如果是的话,则说明这个客户端链接很重要,同样app也就很重要,优先级重新设置为FOREGROUND_APP_ADJ
4.3.2.8 computeOomAdjLocked 关于provider的处理
和处理service基本一致,不同的是,ContentProviderRecord对象中有一个externals对象,这是一个int值,代表了连接该provider的非framework的进程id,也就是说可以使用native写一个linux程序,使用android所提供的provider底层库访问provider,而这个external就是非framework进程的id号,如果这种情况存在,则调整adj为最高。
4.4 ams内部内存回收的规则
4.4.1 进程类别
前台进程(foreground process)
- 正在和用户交互的activity,即执行了onResume
- 包含一个service,该service正在服务于和用户交互的activity
- 包含一个service,该servie正在执行oncreate,onstart或者ondestroy
- 包含一个receiver,正在执行onReceive方法
可视进程(visible process)
- 没有和用户交互,但用户可以看见该activity的窗口,比如一个activity上面弹出一个对话框的情况
- 包含一个service,该service服务于可视的activity,也就是看得见却不能交互的actiity
服务进程(service process)
- 使用startService启动的service对象,所在的进程都是服务进程,当然如果该service满足上面的条件,则会相应的提升优先级
后台进程(background process)
- 不满足上面的三个条件,同时该进程中还包含一些不可见的activity,这些进程不影响正在和用户交互的activity
空进程(empty process)
- 不包含任何component,之所以保留它是为了减少重新创建该进程的开销,创建空进程的过程包括创建进程和加载应用的资源文件,都是很耗时的。
如果不支持oom,则ams使用自己的规则
4.5 客户进程回收内存
1.Ams调用scheduleAppGcLocked方法时会通知指定的app对象进行内存回收。
该函数首先检查app上一次进行gc的时间,并和当前时间进行对比,如果还没超过最小间隔,则将指定的app加入到mProcessesToGc列表中,
2.如果超过了最小时间间隔,则从mProcessesToGc列表中取出下一个app,并发送一个延迟消息,处理该消息的函数是performAppGcsIfAppropriate函数,该函数内部先判断是否合适进行内存回收,如果可以则调用performAppGcsLoceked,否则再次发送一个异步消息。
3.在performAppGcsLoceked内部,使用while循环,在mProcessesToGc列表中逐个取出每个需要进行gc的ProcessRecord对象,并对该对象执行performAppGcLocked(app)方法
4.performAppGcLocked先判断reportLowMemory标志是否为true,如果是则调用app.thread.scheduleLowMemory方法处理,否则调用app.thread.processInBackground方法
4.5.1 scheduleLowMemory
1.scheduleLowMemory会发一个消息,处理该消息的函数是handleLowMemory
2.回调该进程所包含的所有组件的onLowMemory方法,
3.调用SQLiteDatabase的静态方法releaseMemory释放SQLite模块占用的内存。
4.使用Canvas的freeCaches释放应用中所有的canvas对象
5.调用BinderInternal.forceGc("mem")释放该进程的Binder对象
4.5.2 processInBackground
1.调用BinderInternal.forceGc("mem")释放该进程的Binder对象