AMS之内存管理

xiaoxiao2021-02-27  213

原文地址: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运行下一个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对象

作者:xihe 链接:http://www.jianshu.com/p/72045d243b44 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

转载请注明原文地址: https://www.6miu.com/read-11518.html

最新回复(0)