AMS之内存管理
来源:互联网 发布:excel去重复数据函数 编辑:程序博客网 时间:2024/06/06 02:28
原文地址:http://www.jianshu.com/p/72045d243b44
参考资料地址:http://book.51cto.com/art/201109/291375.htm
内存管理包括两个部分
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运行下一个activity4.对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,否则返回false3.如果不支持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对象
作者:xihe
链接:http://www.jianshu.com/p/72045d243b44
來源:简书
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
- AMS之内存管理
- 操作系统之内存管理
- Android之内存管理
- OC之内存管理
- cocos2dx 之内存管理
- c++之内存管理
- OC之内存管理
- jvm之内存管理
- 操作系统之内存管理
- 简单之内存管理
- OC之内存管理
- cocos2dx 之内存管理
- Android之内存管理
- OC之内存管理
- OC之内存管理
- 虚拟机之内存管理
- iOS之内存管理
- iOS之内存管理
- Flume--Load Balancing Sink Processor
- Android仿京东评论多图片选择器
- 字符串大小写分开打印 -- 漫漫算法路 刷题篇
- Linux下判断磁盘是SSD还是HDD的几种方法
- 回归评价指标:均方误差根(RMSE)和R平方(R2)
- AMS之内存管理
- sadfghjk
- java内存模型
- Birthday present
- Es6 类的关键 super、static、constructor、new.targe
- (多核DSP快速入门)1.创建简单的多核DSP项目HelloWorld
- java中的反射机制
- python urlopen 超时
- 82. Remove Duplicates from Sorted List II