AMS之内存管理

内存管理包括两个部分
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对象

你可能感兴趣的:(AMS之内存管理)