Android系统以Linux内核为基础,所以对于进程的管理自然离不开Linux本身提供的机制。例如:
对于Android来说,进程管理的主要内容包括以下几个部分内容:
本文会专门讲解进程的创建,其余部分将在后面的文章中讲解。
为了便于下文的讲解,这里先介绍一下Android系统中牵涉到进程创建的几个主要模块。
同时为了便于读者更详细的了解这些模块,这里也同时提供了这些模块的代码路径。
这里提到的代码路径是指AOSP的源码数中的路径。
关于如何获取AOSP源码请参见这里:Downloading the Source。
本文以Android N版本的代码为示例,所用到的Source Code Tags是:android-7.0.0_r1。
相关模块:
代码路径:frameworks/base/cmds/app_process
说明:app_process是一个可执行程序,该程序的主要作用是启动zygote和system_server进程。
代码路径:frameworks/base/core/java/com/android/internal/os/ZygoteInit.java
说明:zygote进程是所有应用进程的父进程,这是系统中一个非常重要的进程,下文我们会详细讲解。
代码路径:frameworks/base/services/core/java/com/android/server/am/
说明:am是ActivityManager的缩写。
这个目录下的代码负责了Android全部四大组件(Activity,Service,ContentProvider,BroadcastReceiver)的管理,并且还掌控了所有应用程序进程的创建和进程的优先级管理。
因此,这个部分的内容将是本系列文章讲解的重点。
Android官方开发网站的这篇文章:Processes and Threads 非常好的介绍了Android系统中进程相关的一些基本概念和重要知识。
在阅读下文之前,请务必将这篇文章浏览一遍。
在Android系统中,进程可以大致分为系统进程和应用进程两大类。
系统进程是系统内置的(例如:init,zygote,system_server进程),属于操作系统必不可少的一部分。系统进程的作用在于:
应用进程是指应用程序运行的进程。这些应用程序可能是系统出厂自带的(例如Launcher,电话,短信等应用),也可能是用户自己安装的(例如:微信,支付宝等)。
系统进程的数量通常是固定的(出厂或者系统升级之后就确定了),并且系统进程通常是一直存活,常驻内存的。系统进程的异常退出将可能导致设备无法正常使用。
而应用程序和应用进程在每个人使用的设备上通常是各不一样的。如何管理好这些不确定的应用进程,就是操作系统本身要仔细考虑的内容。也是衡量一个操作系统好坏的标准之一。
在本文中,我们会介绍init,zygote和system_server三个系统进程。
除此之外,本系列文章将会把主要精力集中在讲解Android系统如何管理应用进程上。
init进程是一切的开始,在Android系统中,所有进程的进程号都是不确定的,唯独init进程的进程号一定是1。
因为这个进程一定是系统起来的第一个进程。
并且,init进程掌控了整个系统的启动逻辑。
我们知道,Android可能运行在各种不同的平台,不同的设备上。因此,启动的逻辑是不尽相同的。 为了适应各种平台和设备的需求,init进程的初始化工作通过init.rc配置文件来管理。
你可以在AOSP源码的system/core/rootdir/路径找到这些配置文件。
配置文件的主入口文件是init.rc,这个文件会通过import引入其他几个文件。
在本文中,我们统称这些文件为init.rc。
init.rc通过Android Init Language来进行配置。 建议读者大致阅读一下其 语法说明 。
init.rc中配置了系统启动的时候该做哪些事情,以及启动哪些系统进程。
这其中有两个特别重要的进程就是:zygote和system_server进程。
init.rc文件会根据平台不一样,选择下面几个文件中的一个来启动zygote进程:
这几个文件的内容是大致一致的,仅仅是为了不同平台服务的。这里我们以init.zygote32.rc的文件为例,来看看其中的内容:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server class main socket zygote stream 660 root system onrestart write /sys/android_power/request_state wake onrestart write /sys/power/state on onrestart restart audioserver onrestart restart cameraserver onrestart restart media onrestart restart netd writepid /dev/cpuset/foreground/tasks /dev/stune/foreground/tasks
在这段配置文件中(如果你不明白这段配置的含义,请阅读一下文档:Android Init Language),启动了一个名称叫做zygote的服务进程。这个进程是通过/system/bin/app_process 这个可执行程序创建的。
并且在启动这个可执行程序的时候,传递了-Xzygote /system/bin --zygote --start-system-server class main 这些参数。
要知道这里到底做了什么,我们需要看一下app_process的源码。
app_process的源码在这个路径:frameworks/base/cmds/app_process/app_main.cpp。
这个文件的main函数的有如下代码:
int main(int argc, char* const argv[]) { ... while (i < argc) { const char* arg = argv[i++]; if (strcmp(arg, "--zygote") == 0) { zygote = true; niceName = ZYGOTE_NICE_NAME; } else if (strcmp(arg, "--start-system-server") == 0) { startSystemServer = true; ... } ... if (!className.isEmpty()) { ... } else { ... if (startSystemServer) { args.add(String8("start-system-server")); } } ... if (zygote) { runtime.start("com.android.internal.os.ZygoteInit", args, zygote); } else if (className) { runtime.start("com.android.internal.os.RuntimeInit", args, zygote); } else { fprintf(stderr, "Error: no class name or --zygote supplied.\n"); app_usage(); LOG_ALWAYS_FATAL("app_process: no class name or --zygote supplied."); return 10; } }
这里会判断,
这段代码是C++实现的。在执行这段代码的时候还没有任何Java的环境。而runtime.start就是启动Java虚拟机,并在虚拟机中启动指定的类。于是接下来的逻辑就在ZygoteInit.java中了。
这个文件的main函数主要代码如下::
public static void main(String argv[]) { ... try { ... boolean startSystemServer = false; String socketName = "zygote"; String abiList = null; for (int i = 1; i < argv.length; i++) { if ("start-system-server".equals(argv[i])) { startSystemServer = true; } else if (argv[i].startsWith(ABI_LIST_ARG)) { ... } } ... registerZygoteSocket(socketName); ... preload(); ... Zygote.nativeUnmountStorageOnInit(); ZygoteHooks.stopZygoteNoThreadCreation(); if (startSystemServer) { startSystemServer(abiList, socketName); } Log.i(TAG, "Accepting command socket connections"); runSelectLoop(abiList); closeServerSocket(); } catch (MethodAndArgsCaller caller) { caller.run(); } catch (RuntimeException ex) { Log.e(TAG, "Zygote died with exception", ex); closeServerSocket(); throw ex; } }
在这段代码中,我们主要关注如下几行:
这里需要说明的是:zygote进程启动之后,会启动一个socket套接字,并通过Looper一直在这个套接字上等待连接。
所有应用进程都是通过发送数据到这个套接字上,然后由zygote进程创建的。
这里还有一点说明的是: 在Zygote进程中,会通过preload函数加载需要应用程序都需要的公共资源。
预先加载这些公共资源有如下两个好处:
preload的资源主要是Framework相关的一些基础类和Resource资源,而这些资源正是所有应用都需要的:
开发者通过Android SDK开发应用所调用的API实现都在Framework中。
static void preload() { Log.d(TAG, "begin preload"); Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "BeginIcuCachePinning"); beginIcuCachePinning(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadClasses"); preloadClasses(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadResources"); preloadResources(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); Trace.traceBegin(Trace.TRACE_TAG_DALVIK, "PreloadOpenGL"); preloadOpenGL(); Trace.traceEnd(Trace.TRACE_TAG_DALVIK); preloadSharedLibraries(); preloadTextResources(); WebViewFactory.prepareWebViewInZygote(); endIcuCachePinning(); warmUpJcaProviders(); Log.d(TAG, "end preload"); }
上文已经提到,zygote进程起来之后会根据需要启动system_server进程。
system_server进程中包含了大量的系统服务。例如:
等等。关于system_server,我们今后会其他的文章中专门讲解,这里不做过多说明。
在本文中,我们只关注system_server中的ActivityManagerService这个系统服务。
上文中提到:zygote进程在启动之后会启动一个socket,然后一直在这个socket等待连接。
而会连接它的就是ActivityManagerService。
因为ActivityManagerService掌控了所有应用进程的创建。
所有应用程序的进程都是由ActivityManagerService通过socket发送请求给Zygote进程,然后由zygote fork创建的。
ActivityManagerService通过Process.start方法来请求zygote创建进程:
public static final ProcessStartResult start(final String processClass, final String niceName, int uid, int gid, int[] gids, int debugFlags, int mountExternal, int targetSdkVersion, String seInfo, String abi, String instructionSet, String appDataDir, String[] zygoteArgs) { try { return startViaZygote(processClass, niceName, uid, gid, gids, debugFlags, mountExternal, targetSdkVersion, seInfo, abi, instructionSet, appDataDir, zygoteArgs); } catch (ZygoteStartFailedEx ex) { Log.e(LOG_TAG, "Starting VM process through Zygote failed"); throw new RuntimeException( "Starting VM process through Zygote failed", ex); } }
这个函数会将启动进程所需要的参数组装好,并通过socket发送给zygote进程。然后zygote进程根据发送过来的参数将进程fork出来。
在ActivityManagerService中,调用Process.start的地方是下面这个方法:
private final void startProcessLocked(ProcessRecord app, String hostingType, String hostingNameStr, String abiOverride, String entryPoint, String[] entryPointArgs) { ... Process.ProcessStartResult startResult = Process.start(entryPoint, app.processName, uid, uid, gids, debugFlags, mountExternal, app.info.targetSdkVersion, app.info.seinfo, requiredAbi, instructionSet, app.info.dataDir, entryPointArgs); ... }
下文中我们会看到,所有四大组件进程的创建,都是调用这里的startProcessLocked这个方法而创建的。
对于每一个应用进程,在ActivityManagerService中,都有一个ProcessRecord与之对应。这个对象记录了应用进程的所有详细状态。
PS:对于ProcessRecord的内部结构,在下一篇文章中,我们会讲解。
为了查找方便,对于每个ProcessRecord会存在下面两个集合中。
/** * All of the applications we currently have running organized by name. * The keys are strings of the application package name (as * returned by the package manager), and the keys are ApplicationRecord * objects. */ final ProcessMapmProcessNames = new ProcessMap ();
/** * All of the processes we currently have running organized by pid. * The keys are the pid running the application. * *NOTE: This object is protected by its own lock, NOT the global * activity manager lock! */ final SparseArray
mPidsSelfLocked = new SparseArray ();
下面这幅图小节了上文的这些内容:
Processes and Threads 提到:
“当某个应用组件启动且该应用没有运行其他任何组件时,Android 系统会使用单个执行线程为应用启动新的 Linux 进程。”
因此,四大组件中的任何一个先起来都会导致应用进程的创建。下文我们就详细看一下,它们启动时,各自是如何导致应用进程的创建的。
PS:四大组件的管理本身又是一个比较大的话题,限于篇幅关系,这里不会非常深入的讲解,这里主要是讲解四大组件与进程创建的关系。
在应用程序中,开发者通过:
这其中,startActivity,startService和sendBroadcast还有一些重载方法。
其实这里提到的所有这些方法,最终都是通过Binder调用到ActivityManagerService中,由其进行处理的。
这里特别说明一下:应用进程和ActivityManagerService所在进程(即system_server进程)是相互独立的,两个进程之间的方法通常是不能直接互相调用的。
而Android系统中,专门提供了Binder框架来提供进程间通讯和方法调用的能力。
调用关系如下图所示:
在ActivityManagerService中,对每一个运行中的Activity都有一个ActivityRecord对象与之对应,这个对象记录Activity的详细状态。
ActivityManagerService中的startActivity方法接受Context.startActivity的请求,该方法代码如下:
@Override public final int startActivity(IApplicationThread caller, String callingPackage, Intent intent, String resolvedType, IBinder resultTo, String resultWho, int requestCode, int startFlags, ProfilerInfo profilerInfo, Bundle bOptions) { return startActivityAsUser(caller, callingPackage, intent, resolvedType, resultTo, resultWho, requestCode, startFlags, profilerInfo, bOptions, UserHandle.getCallingUserId()); }
Activity的启动是一个非常复杂的过程。这里我们简单介绍一下背景知识:
Activity的管理结构如下图所示:
在Activity启动的最后,会将前一个Activity pause,将新启动的Activity resume以便被用户看到。
在这个时候,如果发现新启动的Activity进程还没有启动,则会通过startSpecificActivityLocked将其启动。整个调用流程如下:
ActivityStackSupervisor.startSpecificActivityLocked 关键代码如下:
void startSpecificActivityLocked(ActivityRecord r, boolean andResume, boolean checkConfig) { // Is this activity's application already running? ProcessRecord app = mService.getProcessRecordLocked(r.processName, r.info.applicationInfo.uid, true); r.task.stack.setLaunchTime(r); if (app != null && app.thread != null) { ... } mService.startProcessLocked(r.processName, r.info.applicationInfo, true, 0, "activity", r.intent.getComponent(), false, false, true); }
这里的ProcessRecord app 描述了Activity所在进程。
Service的启动相对于Activity来说要简单一些。
在ActivityManagerService中,对每一个运行中的Service都有一个ServiceRecord对象与之对应,这个对象记录Service的详细状态。
ActivityManagerService中的startService方法处理Context.startServiceAPI的请求,相关代码:
@Override public ComponentName startService(IApplicationThread caller, Intent service, String resolvedType, String callingPackage, int userId) throws TransactionTooLargeException { ... synchronized(this) { final int callingPid = Binder.getCallingPid(); final int callingUid = Binder.getCallingUid(); final long origId = Binder.clearCallingIdentity(); ComponentName res = mServices.startServiceLocked(caller, service, resolvedType, callingPid, callingUid, callingPackage, userId); Binder.restoreCallingIdentity(origId); return res; } }
这段代码中的mServices对象是ActiveServices类型的,这个类专门负责管理活动的Service。
启动Service的调用流程如下:
ActiveServices.bringUpServiceLocked会判断如果Service所在进程还没有启动,
则通过ActivityManagerService.startProcessLocked将其启动。相关代码如下:
// Not running -- get it started, and enqueue this service record // to be executed when the app comes up. if (app == null && !permissionsReviewRequired) { if ((app=mAm.startProcessLocked(procName, r.appInfo, true, intentFlags, "service", r.name, false, isolated, false)) == null) { String msg = "Unable to launch app " + r.appInfo.packageName + "/" + r.appInfo.uid + " for service " + r.intent.getIntent() + ": process is bad"; Slog.w(TAG, msg); bringDownServiceLocked(r); return msg; } if (isolated) { r.isolatedProc = app; } }
这里的mAm 就是ActivityManagerService。
在ActivityManagerService中,对每一个运行中的ContentProvider都有一个ContentProviderRecord对象与之对应,这个对象记录ContentProvider的详细状态。
开发者通过ContentResolver中的insert, delete, update, query这些API来使用ContentProvider。在ContentResolver的实现中,无论使用这里的哪个接口,ContentResolver都会先通过acquireProvider 这个方法来获取到一个类型为IContentProvider的远程接口。这个远程接口对接了ContentProvider的实现提供方。
同一个ContentProvider可能同时被多个模块使用,而调用ContentResolver接口的进程只是ContentProvider的一个客户端而已,真正的ContentProvider提供方是运行自身的进程中的,两个进程的通讯需要通过Binder的远程接口形式来调用。如下图所示:
ContentResolver.acquireProvider 最终会调用到ActivityManagerService.getContentProvider中,该方法代码如下:
@Override public final ContentProviderHolder getContentProvider( IApplicationThread caller, String name, int userId, boolean stable) { enforceNotIsolatedCaller("getContentProvider"); if (caller == null) { String msg = "null IApplicationThread when getting content provider " + name; Slog.w(TAG, msg); throw new SecurityException(msg); } // The incoming user check is now handled in checkContentProviderPermissionLocked() to deal // with cross-user grant. return getContentProviderImpl(caller, name, null, stable, userId); }
而在getContentProviderImpl这个方法中,会判断对应的ContentProvider进程有没有启动,
如果没有,则通过startProcessLocked方法将其启动。
开发者通过Context.sendBroadcast接口来发送广播。ActivityManagerService.broadcastIntent 方法了对应广播发送的处理。
广播是一种一对多的消息形式,广播接受者的数量是不确定的。因此发送广播本身可能是一个很耗时的过程(因为要逐个通知)。
在ActivityManagerService内部,是通过队列的形式来管理广播的:
在ActivityManagerService中,如果收到了一个发送广播的请求,会先创建一个BroadcastRecord接着将其放入BroadcastQueue中。
然后通知队列自己去处理这个广播。然后ActivityManagerService自己就可以继续处理其他请求了。
广播队列本身是在另外一个线程处理广播的发送的,这样保证的ActivityManagerService主线程的负载不会太重。
在BroadcastQueue.processNextBroadcast(boolean fromMsg) 方法中真正实现了通知广播事件到接受者的逻辑。在这个方法,如果发现接受者(即BrodcastReceiver)还没有启动,便会通过ActivityManagerService.startProcessLocked 方法将其启动。相关如下所示:
final void processNextBroadcast(boolean fromMsg) { ... // Hard case: need to instantiate the receiver, possibly // starting its application process to host it. ResolveInfo info = (ResolveInfo)nextReceiver; ComponentName component = new ComponentName( info.activityInfo.applicationInfo.packageName, info.activityInfo.name); ... // Not running -- get it started, to be executed when the app comes up. if (DEBUG_BROADCAST) Slog.v(TAG_BROADCAST, "Need to start app [" + mQueueName + "] " + targetProcess + " for broadcast " + r); if ((r.curApp=mService.startProcessLocked(targetProcess, info.activityInfo.applicationInfo, true, r.intent.getFlags() | Intent.FLAG_FROM_BACKGROUND, "broadcast", r.curComponent, (r.intent.getFlags()&Intent.FLAG_RECEIVER_BOOT_UPGRADE) != 0, false, false)) == null) { // Ah, this recipient is unavailable. Finish it if necessary, // and mark the broadcast record as ready for the next. Slog.w(TAG, "Unable to launch app " + info.activityInfo.applicationInfo.packageName + "/" + info.activityInfo.applicationInfo.uid + " for broadcast " + r.intent + ": process is bad"); logBroadcastReceiverDiscardLocked(r); finishReceiverLocked(r, r.resultCode, r.resultData, r.resultExtras, r.resultAbort, false); scheduleBroadcastsLocked(); r.state = BroadcastRecord.IDLE; return; } mPendingBroadcast = r; mPendingBroadcastRecvIndex = recIdx; } }
至此,四大组件的启动就已经分析完了。
进程的优先级反应了系统对于进程重要性的判定。
在Android系统中,进程的优先级影响着以下三个因素:
本文会主要讲解系统对于进程优先级的判断依据和计算方法。
在Processes and Threads (如果你还没有阅读,请立即阅读一下这篇文章)一文中,我们已经了解到,系统对于进程的优先级有如下五个分类:
实际上这只是一个粗略的划分。在系统的内部实现中,优先级远不止这么五种。
我们来简单列一下应用组件与进程的相关信息:
ProcessRecord简化图如下所示:
在ProcessRecord中,详细记录了应用组件的相关信息,相关代码如下:
// all activities running in the process final ArrayListactivities = new ArrayList<>(); // all ServiceRecord running in this process final ArraySet services = new ArraySet<>(); // services that are currently executing code (need to remain foreground). final ArraySet executingServices = new ArraySet<>(); // All ConnectionRecord this process holds final ArraySet connections = new ArraySet<>(); // all IIntentReceivers that are registered from this process. final ArraySet receivers = new ArraySet<>(); // class (String) -> ContentProviderRecord final ArrayMap pubProviders = new ArrayMap<>(); // All ContentProviderRecord process is using final ArrayList conProviders = new ArrayList<>();
这里的:
而:
连接就是对于客户端使用状态的记录,对于Service和ContentProvider是类似的,每有一个客户端就需要记录一个连接。连接的意义在于:连接的客户端的进程优先级会影响被使用的Service和ContentProvider所在进程的优先级。 例如:当一个后台的Service正在被一个前台的Activity使用,那么这个后台的Service就需要设置一个较高的优先级以便不会被回收。(否则后台Service进程一旦被回收,便会对前台的Activity造成影响。)
而所有这些组件的状态就是其所在进程优先级的决定性因素。 组件的状态是指:
对于每一个运行中的进程,Linux内核都通过proc文件系统暴露这样一个文件来允许其他程序修改指定进程的优先级:
/proc/[pid]/oom_score_adj。(修改这个文件需要root权限)
这个文件允许的值的范围是:-1000 ~ +1000之间。值越小,表示进程越重要。
当内存非常紧张时,系统便会遍历所有进程,以确定哪个进程需要被杀死以回收内存,此时便会读取oom_score_adj 这个文件的值。关于这个值的使用,在后面讲解进程回收的的时候,我们会详细讲解。
PS:在Linux 2.6.36之前的版本中,Linux 提供调整优先级的文件是/proc/[pid]/oom_adj。这个文件允许的值的范围是-17 ~ +15之间。数值越小表示进程越重要。 这个文件在新版的Linux中已经废弃。
但你仍然可以使用这个文件,当你修改这个文件的时候,内核会直接进行换算,将结果反映到oom_score_adj这个文件上。
Android早期版本的实现中也是依赖oom_adj这个文件。但是在新版本中,已经切换到使用oom_score_adj这个文件。
ProcessRecord中下面这些属性反应了oom_score_adj的值:
int maxAdj; // Maximum OOM adjustment for this process int curRawAdj; // Current OOM unlimited adjustment for this process int setRawAdj; // Last set OOM unlimited adjustment for this process int curAdj; // Current OOM adjustment for this process int setAdj; // Last set OOM adjustment for this process
maxAdj 指定了该进程允许的oom_score_adj最大值。这个属性主要是给系统应用和常驻内存的进程使用,这些进程的优先级的计算方法与应用进程的计算方法不一样,通过设定maxAdj保证这些进程一直拥有较高的优先级(在后面”优先级的算法“中,我们会看到对于这个属性的使用)。
除此之外,还有四个属性。
这其中,curXXX这一组记录了这一次优先级计算的结果。在计算完成之后,会将curXXX复制给对应的setXXX这一组上进行备份。 (下文的其他属性也会看到curXXX和setXXX的形式,和这里的原理是一样的。)
另外,xxxRawAdj记录了没有经过限制的adj值,“没有经过限制”是指这其中的值可能是超过了oom_score_adj文件所允许的范围(-1000 ~ 1000)。
为了便于管理,ProcessList.java中预定义了oom_score_adj的可能取值。
其实这里的预定义值也是对应用进程的一种分类,它们是:
static final int UNKNOWN_ADJ = 1001; // 未知进程 static final int PREVIOUS_APP_ADJ = 700; // 前一个应用 static final int HOME_APP_ADJ = 600; // 桌面进程 static final int SERVICE_ADJ = 500; // 包含了Service的进程 static final int HEAVY_WEIGHT_APP_ADJ = 400; // 重量级进程 static final int BACKUP_APP_ADJ = 300; // 备份应用进程 static final int PERCEPTIBLE_APP_ADJ = 200; // 可感知的进程 static final int VISIBLE_APP_ADJ = 100; // 可见进程 static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1; static final int FOREGROUND_APP_ADJ = 0; // 前台进程 static final int PERSISTENT_SERVICE_ADJ = -700; // 常驻服务进程 static final int PERSISTENT_PROC_ADJ = -800; // 常驻应用进程 static final int SYSTEM_ADJ = -900; // 系统进程 static final int NATIVE_ADJ = -1000; // native系统进程
这里我们看到,FOREGROUND_APP_ADJ = 0,这个是前台应用进程的优先级。这是用户正在交互的应用,它们是很重要的,系统不应当把它们回收了。
FOREGROUND_APP_ADJ = 0是普通应用程序能够获取到的最高优先级。
而VISIBLE_APP_ADJ,PERCEPTIBLE_APP_ADJ,PREVIOUS_APP_ADJ这几个级别的优先级就逐步降低了。
VISIBLE_APP_ADJ是具有可见Activity进程的优先级:同一时刻,不一定只有一个Activity是可见的,如果前台Activity设置了透明属性,那么背后的Activity也是可见的。
PERCEPTIBLE_APP_ADJ是指用户可感知的进程,可感知的进程包括:
另外,PREVIOUS_APP_ADJ描述的是前一个应用的优先级。所谓“前一个应用”是指:在启动新的Activity时,如果新启动的Activity是属于一个新的进程的,那么当前即将被stop的Activity所在的进程便会成为“前一个应用”进程。
而HEAVY_WEIGHT_APP_ADJ 描述的重量级进程是指那些通过Manifest指明不能保存状态的应用进程。
除此之外,Android系统中,有一些系统应用会常驻内存,这些应用通常是系统实现的一部分,如果它们不存在,系统将处于比较奇怪的状态,例如SystemUI(状态栏,Keyguard都处于这个应用中)。
所以它们的优先级比所有应用进程的优先级更高:PERSISTENT_SERVICE_ADJ = -700,PERSISTENT_PROC_ADJ = -800。
另外,还有一些系统服务的实现,如果这些系统服务不存在,系统将无法工作,所以这些应用的优先级最高,几乎是任何任何时候都需要存在的:SYSTEM_ADJ = -900,NATIVE_ADJ = -1000。
内核负责了进程的CPU调度,所有运行中的进程并非能平等的能获取相等的时间片。在ProcessRecord中,通过Schedule Group来记录进程的调度组:
int curSchedGroup; // Currently desired scheduling class int setSchedGroup; // Last set to background scheduling class
它们可能的取值定义在ProcessList.java中:
// Activity manager's version of Process.THREAD_GROUP_BG_NONINTERACTIVE static final int SCHED_GROUP_BACKGROUND = 0; // Activity manager's version of Process.THREAD_GROUP_DEFAULT static final int SCHED_GROUP_DEFAULT = 1; // Activity manager's version of Process.THREAD_GROUP_TOP_APP static final int SCHED_GROUP_TOP_APP = 2; // Activity manager's version of Process.THREAD_GROUP_TOP_APP // Disambiguate between actual top app and processes bound to the top app static final int SCHED_GROUP_TOP_APP_BOUND = 3;
进程的状态会影响虚拟机对于进程的内存分配和垃圾回收策略,ProcessRecord中的下面这几个属性记录了进程的状态:
int curProcState; // Currently computed process state int repProcState; // Last reported process state int setProcState; // Last set process state in process tracker int pssProcState; // Currently requesting pss for
这些属性可能的取值定义在ActivityManager中,这些常量的名称已经说明了其作用:
public static final int PROCESS_STATE_NONEXISTENT = -1; public static final int PROCESS_STATE_PERSISTENT = 0; public static final int PROCESS_STATE_PERSISTENT_UI = 1; public static final int PROCESS_STATE_TOP = 2; public static final int PROCESS_STATE_BOUND_FOREGROUND_SERVICE = 3; public static final int PROCESS_STATE_FOREGROUND_SERVICE = 4; public static final int PROCESS_STATE_TOP_SLEEPING = 5; public static final int PROCESS_STATE_IMPORTANT_FOREGROUND = 6; public static final int PROCESS_STATE_IMPORTANT_BACKGROUND = 7; public static final int PROCESS_STATE_BACKUP = 8; public static final int PROCESS_STATE_HEAVY_WEIGHT = 9; public static final int PROCESS_STATE_SERVICE = 10; public static final int PROCESS_STATE_RECEIVER = 11; public static final int PROCESS_STATE_HOME = 12; public static final int PROCESS_STATE_LAST_ACTIVITY = 13; public static final int PROCESS_STATE_CACHED_ACTIVITY = 14; public static final int PROCESS_STATE_CACHED_ACTIVITY_CLIENT = 15; public static final int PROCESS_STATE_CACHED_EMPTY = 16;
前文已经提到,系统会对处于不同状态的进程设置不同的优先级。但实际上,进程的状态是一直在变化中的。例如:用户可以随时会启动一个新的Activity,或者将一个前台的Activity切换到后台。在这个时候,发生状态变化的Activity的所在进程的优先级就需要进行更新。
并且,Activity可能会使用其他的Service或者ContentProvider。当Activity的进程优先级发生变化的时候,它所使用的Service或者ContentProvider的优先级也应当发生变化。
ActivityManagerService中有如下两个方法用来更新进程的优先级:
第一个方法是针对指定的单个进程更新优先级。第二个是对所有进程更新优先级。
在下面的这些情况下,需要对指定的应用进程更新优先级:
final boolean updateOomAdjLocked(ProcessRecord app) 被调用的关系如下图所示:
在有些情况下,系统需要对所有应用进程的优先级进行更新,譬如:
final void updateOomAdjLocked() 被调用的关系图如下所示:
ActivityManagerService中的computeOomAdjLocked方法负责计算进程的优先级,这个方法总计约700行,执行流程主要包含如下10个步骤:
下面我们来详细看其中的每一个步骤:
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.maxAdj <= ProcessList.FOREGROUND_APP_ADJ) { app.adjType = "fixed"; app.adjSeq = mAdjSeq; app.curRawAdj = app.maxAdj; app.foregroundActivities = false; app.curSchedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT; app.systemNoUi = true; if (app == TOP_APP) { app.systemNoUi = false; app.curSchedGroup = ProcessList.SCHED_GROUP_TOP_APP; app.adjType = "pers-top-activity"; } else if (activitiesSize > 0) { for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.visible) { app.systemNoUi = false; } } } if (!app.systemNoUi) { app.curProcState = ActivityManager.PROCESS_STATE_PERSISTENT_UI; } return (app.curAdj=app.maxAdj); }
int adj; int schedGroup; int procState; boolean foregroundActivities = false; BroadcastQueue queue; if (app == TOP_APP) { adj = ProcessList.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) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.adjType = "instrumentation"; procState = ActivityManager.PROCESS_STATE_FOREGROUND_SERVICE; } else if ((queue = isReceivingBroadcast(app)) != null) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = (queue == mFgBroadcastQueue) ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "broadcast"; procState = ActivityManager.PROCESS_STATE_RECEIVER; } else if (app.executingServices.size() > 0) { adj = ProcessList.FOREGROUND_APP_ADJ; schedGroup = app.execServicesFg ? ProcessList.SCHED_GROUP_DEFAULT : ProcessList.SCHED_GROUP_BACKGROUND; app.adjType = "exec-service"; procState = ActivityManager.PROCESS_STATE_SERVICE; } else { schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; adj = cachedAdj; procState = ActivityManager.PROCESS_STATE_CACHED_EMPTY; app.cached = true; app.empty = true; app.adjType = "cch-empty"; }
if (!foregroundActivities && activitiesSize > 0) { int minLayer = ProcessList.VISIBLE_APP_LAYER_MAX; for (int j = 0; j < activitiesSize; j++) { final ActivityRecord r = app.activities.get(j); if (r.app != app) { Log.e(TAG, "Found activity " + r + " in proc activity list using " + r.app + " instead of expected " + app); if (r.app == null || (r.app.uid == app.uid)) { // Only fix things up when they look sane r.app = app; } else { continue; } } if (r.visible) { // App has a visible activity; only upgrade adjustment. if (adj > ProcessList.VISIBLE_APP_ADJ) { adj = ProcessList.VISIBLE_APP_ADJ; app.adjType = "visible"; } if (procState > PROCESS_STATE_CUR_TOP) { procState = PROCESS_STATE_CUR_TOP; } schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false; app.empty = false; foregroundActivities = true; if (r.task != null && minLayer > 0) { final int layer = r.task.mLayerRank; if (layer >= 0 && minLayer > layer) { minLayer = layer; } } break; } else if (r.state == ActivityState.PAUSING || r.state == ActivityState.PAUSED) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "pausing"; } if (procState > PROCESS_STATE_CUR_TOP) { procState = PROCESS_STATE_CUR_TOP; } schedGroup = ProcessList.SCHED_GROUP_DEFAULT; app.cached = false; app.empty = false; foregroundActivities = true; } else if (r.state == ActivityState.STOPPING) { if (adj > ProcessList.PERCEPTIBLE_APP_ADJ) { adj = ProcessList.PERCEPTIBLE_APP_ADJ; app.adjType = "stopping"; } if (!r.finishing) { if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; } } app.cached = false; app.empty = false; foregroundActivities = true; } else { if (procState > ActivityManager.PROCESS_STATE_CACHED_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_CACHED_ACTIVITY; app.adjType = "cch-act"; } } } if (adj == ProcessList.VISIBLE_APP_ADJ) { adj += minLayer; } }
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; } }
if (app == mHeavyWeightProcess) { if (adj > ProcessList.HEAVY_WEIGHT_APP_ADJ) { adj = ProcessList.HEAVY_WEIGHT_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false; app.adjType = "heavy"; } if (procState > ActivityManager.PROCESS_STATE_HEAVY_WEIGHT) { procState = ActivityManager.PROCESS_STATE_HEAVY_WEIGHT; } } if (app == mHomeProcess) { if (adj > ProcessList.HOME_APP_ADJ) { 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; } } if (app == mPreviousProcess && app.activities.size() > 0) { if (adj > ProcessList.PREVIOUS_APP_ADJ) { adj = ProcessList.PREVIOUS_APP_ADJ; schedGroup = ProcessList.SCHED_GROUP_BACKGROUND; app.cached = false; app.adjType = "previous"; } if (procState > ActivityManager.PROCESS_STATE_LAST_ACTIVITY) { procState = ActivityManager.PROCESS_STATE_LAST_ACTIVITY; } } if (false) Slog.i(TAG, "OOM " + app + ": initial adj=" + adj + " reason=" + app.adjType); app.adjSeq = mAdjSeq; app.curRawAdj = adj; app.hasStartedServices = false; if (mBackupTarget != null && app == mBackupTarget.app) { 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; } }
for (int is = app.services.size()-1; is >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); is--) { ServiceRecord s = app.services.valueAt(is); if (s.startRequested) { app.hasStartedServices = true; if (procState > ActivityManager.PROCESS_STATE_SERVICE) { procState = ActivityManager.PROCESS_STATE_SERVICE; } if (app.hasShownUi && app != mHomeProcess) { if (adj > ProcessList.SERVICE_ADJ) { app.adjType = "cch-started-ui-services"; } } else { if (now < (s.lastActivity + ActiveServices.MAX_SERVICE_INACTIVITY)) { if (adj > ProcessList.SERVICE_ADJ) { adj = ProcessList.SERVICE_ADJ; app.adjType = "started-services"; app.cached = false; } } if (adj > ProcessList.SERVICE_ADJ) { app.adjType = "cch-started-services"; } } } for (int conni = s.connections.size()-1; conni >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); conni--) { ArrayListclist = s.connections.valueAt(conni); for (int i = 0; i < clist.size() && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP);
for (int provi = app.pubProviders.size()-1; provi >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); provi--) { ContentProviderRecord cpr = app.pubProviders.valueAt(provi); for (int i = cpr.connections.size()-1; i >= 0 && (adj > ProcessList.FOREGROUND_APP_ADJ || schedGroup == ProcessList.SCHED_GROUP_BACKGROUND || procState > ActivityManager.PROCESS_STATE_TOP); i--) { ContentProviderConnection conn = cpr.connections.get(i); ProcessRecord client = conn.client; if (client == app) { // Being our own client is not interesting. continue; } int clientAdj = computeOomAdjLocked(client, cachedAdj, TOP_APP, doingAll, now); ...
这里想专门说明一下的是,在这一步还会对Service进程做ServiceB的区分。系统将Service进程分为ServiceA和ServiceB。ServiceA是相对来说较新的Service,而ServiceB相对来说是比较“老旧”的,对用户来说可能是不那么感兴趣的,因此ServiceB的优先级会相对低一些。
static final int SERVICE_B_ADJ = 800; static final int SERVICE_ADJ = 500;
而ServiceB的标准是:app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); 即:所有Service进程的前1/3为ServiceA,剩下为ServiceB。
if (adj == ProcessList.SERVICE_ADJ) { if (doingAll) { app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); mNewNumServiceProcs++; if (!app.serviceb) { if (mLastMemoryLevel > ProcessStats.ADJ_MEM_FACTOR_NORMAL && app.lastPss >= mProcessList.getCachedRestoreThresholdKb()) { app.serviceHighRam = true; app.serviceb = true; } else { mNewNumAServiceProcs++; } } else { app.serviceHighRam = false; } } if (app.serviceb) { adj = ProcessList.SERVICE_B_ADJ; } } app.curRawAdj = adj;
app.curAdj = app.modifyRawOomAdj(adj); app.curSchedGroup = schedGroup; app.curProcState = procState; app.foregroundActivities = foregroundActivities;
优先级的生效是指:将计算出来的优先级真正应用到系统中,applyOomAdjLocked 方法负责了此项工作。
前文中我们提到,优先级意味着三个方面,这里的生效就对应了这三个方面:
前言中我们提到,“优先级反应了系统对于进程重要性的判定。”
那么,系统如何评价进程的优先级,便是系统本身一个很重要的特性。了解系统的这一特性对于我们开发应用程序,以及对于应用程序运行的行为分析是很有意义的。
系统在判定优先级的时候,应当做到公平公正,并且不能让开发者有机可乘。
“公平公正”是指系统需要站在一个中间人的状态下,不偏倚任何一个应用,公正的将系统资源分配给真正需要的进程。并且在系统资源紧张的时候,回收不重要的进程。
通过上文的分析,我们看到,Android系统认为“重要”的进程主要有三类:
不过对于这一点是有改进的空间的,例如,可以引入用户习惯的分析:如果是用户频繁使用的应用,可以给予这些应用更高的优先级以提升这些应用的响应速度。目前,国内一些Android定制厂商已经开始做这类功能的支持。
“不能让开发者有机可乘”是指:系统对于进程优先级的判定的因素应当是不能被开发者利用的。因为一旦开发者可以利用,每个开发者都肯定会将自己的设置为高优先级,来抢占更多的资源。
需要说明的是,Android在这个方面是存在缺陷的:在Android系统上,可以通过startForeground拿到前台的优先级的。后来Google也意识到这个问题,于是在API Level 18以上的版本上,调用startForeground这个API会在通知栏显示一条通知以告知用户。但是,这个改进是有Bug的:开发者可以同时通过startForeground启动两个Service,指定同样的通知id,然后退出其中一个,这样应用的不会在通知栏显示通知图标,并且拿到了前台的优先级。这个便是让开发者“有机可乘”了。
内存是系统中非常宝贵的资源,即便如今的移动设备上,内存已经达到4G甚至6G的级别,但对于内存的回收也依然重要,因为在Android系统上,同时运行的进程有可能会有几十甚至上百个之多。
如何将系统内存合理的分配给每个进程,以及如何进行内存回收,便是操作系统需要处理的问题之一。
本文会讲解Android系统中内存回收相关的知识。
对于内存回收,主要可以分为两个层次:
这其中,进程内的内存回收主要分为两个方面:
而进程级的内存回收主要是依靠系统中的两个模块,它们是:
在特定场景下,他们都会通过杀死进程来进行内存回收。
下图描述了几种内存回收机制:
在Android系统中,进程可以大致分为系统进程和应用进程两大类。
系统进程是系统内置的(例如:init,zygote,system_server进程),属于操作系统必不可少的一部分。系统进程的作用在于:
应用进程是指应用程序运行的进程。这些应用程序可能是系统出厂自带的(例如Launcher,电话,短信等应用),也可能是用户自己安装的(例如:微信,支付宝等)。
Android中应用进程通常都运行在Java虚拟机中。在Android 5.0之前的版本,这个虚拟机是Dalvik,5.0及之后版本,Android引入了新的虚拟机,称作Android Runtime,简称“ART”。
关于ART和Dalvik可以参见这里:ART and Dalvik。无论是Dalvik还是ART,本身都具有垃圾回收的能力,关于这一点,我们在后面专门讲解。
Android的应用程序都会依赖一些公共的资源,例如:Android SDK提供的类和接口,以及Framework公开的图片,字符串等。为了达到节省内存的目的,这些资源在内存中并不是每个应用进程单独一份拷贝。而是会在所有应用之间共享,因为所有应用进程都是作为Zygote进程fork出来的子进程。关于这部分内容,我们已经在Android系统中的进程管理:进程的创建一文中讲解过。
在Java语言中,通过new创建的对象都会在堆中分配内存。应用程序堆的大小是有限的。系统会根据设备的物理内存大小来确定每个应用程序所允许使用的内存大小,一旦应用程序使用的内存超过这个大小,便会发生OutOfMemoryError。
因此开发者需要关心应用的内存使用状况。关于如何监测应用程序的内存使用,可以参见这里:Investigating Your RAM Usage。
下面是一些与内存相关的开发者API,它们是Android SDK的一部分。
Android系统会根据当前的系统内存状态和应用的自身状态对应用进行通知。这种通知的目的是希望应用能够感知到系统和自身的状态变化,以便开发者可以更准确的把握应用的运行。
例如:在系统内存充足时,为了提升响应性能,应用可以缓存更多的资源。但是当系统内存紧张时,开发者应当释放一定的资源来缓解内存紧张的状态。
ComponentCallbacks2接口中的void onTrimMemory(int level) 回调函数用来接收这个通知。关于这一点,在“开发者的内存回收”一节,我们会详细讲解。
ActivityManager,从名称中就可以看出,这个类是用来管理Activity的系统服务。但这个类中也包含了很多运行时状态查询的接口,这其中就包括与内存相关的几个:
Java应用程序都会有一个Runtime接口的实例,通过这个实例可以查询运行时的一些状态,与内存相关的接口有:
垃圾回收是指:虚拟机会监测应用程序的对象创建和使用,并在一些特定的时候销毁无用的对象以回收内存。
垃圾回收的基本想法是要找出虚拟机中哪些对象已经不会再被使用然后将其释放。其最常用的算法有下面两种:
引用计数算法
引用计数算法是为每个对象维护一个被引用的次数:对象刚创建时的初始引用计数为0,每次被一个对象引用时,引用计数加1,反之减1。当一个对象的引用计数重新回到0时便可以认为是不会被使用的,这些对象便可以被垃圾回收。
读者可能马上会想到,当有两个对象互相引用时,这时引用计数该如何计算。关于这部分内容,这里不再展开讲解。有兴趣的读者可以查询Google或者维基百科:Garbage collection
对象追踪算法是通过GC root类型的对象为起点,追踪所有被这些对象所引用的对象,并顺着这些被引用的对象继续往下追踪,在追踪的过程中,对所有被追踪到的对象打上标记。
而剩下的那些没有被打过标记的对象便可以认为是没有被使用的,因此这些对象可以将其释放。
这里提到的的GC root类型的对象有四类:
下面这幅图描述了这种算法:
a)表示算法开始时,所有对象的标记为false,然后以GC root为起点开始追踪和打标记,b)中被追踪到的对象打上了标记。剩下的没有打上标记的对象便可以释放了。算法结束之后,c)中将所有对象的标记全部置为false。下一轮计算时,重新以GC root开始追踪。
Dalvik虚拟机用的就是对象追踪算法,这里是其源码:MarkSweep.cpp
内存回收并不是仅仅是系统的事情,作为开发者,也需要在合适的场合下进行内存释放。无节制的消耗内存将导致应用程序OutOfMemoryError。
上文中提到,虚拟机的垃圾回收会回收那些不会再被使用到的对象。因此,开发者所需要做的就是:当确定某些对象不会再被使用时,要主动释放对其引用,这样虚拟机才能将其回收。对于不再被用到对象,仍然保持对其引用导致其无法释放,将导致内存泄漏的发生。
为了更好的进行内存回收,系统会一些场景下会通知应用,希望应用能够配合进行一些内存的释放。
ComponentCallbacks2接口中的 void onTrimMemory(int level)回调就是用来接收这个事件的。
Activity, Service, ContentProvider和Application都实现了这个接口,因此这些类的子类都可以接收这个事件。
onTrimMemory回调的参数是一个级别,系统会根据应用本身的状态以及系统的内存状态发送不同的级别,具体的包括:
这里是这个方法实现的示例代码:Release memory in response to events
在前面的文章中我们提到过:ActivityManagerService负责管理所有的应用进程。
而这里的通知也是来自ActivityManagerService。在updateOomAdjLocked的时候,ActivityManagerService会根据系统内存以及应用的状态通过app.thread.scheduleTrimMemory发送通知给应用程序。
这里的app是ProcessRecord,即描述应用进程的对象,thread是应用的主线程。而scheduleTrimMemory是通过Binder IPC的方式将消息发送到应用进程上。这些内容在前面的文章中已经介绍过,如果觉得陌生,可以阅读一下前面两篇文章。
在ActivityThread中(这个是应用程序的主线程),接受到这个通知之后,便会遍历应用进程中所有能接受这个通知的组件,然后逐个回调通知。
相关代码如下:
final void handleTrimMemory(int level) { if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level); ArrayListcallbacks = collectComponentCallbacks(true, null); final int N = callbacks.size(); for (int i = 0; i < N; i++) { callbacks.get(i).onTrimMemory(level); } WindowManagerGlobal.getInstance().trimMemory(level); }
前面提到的机制都是在进程内部通过释放对象来进行内存回收。
而实际上,系统中运行的进程数量,以及每个进程所消耗的内存都是不确定的。
在极端的情况下,系统的内存可能处于非常严峻的状态,假设这个时候所有进程都不愿意释放内存,系统将会卡死。
为了使系统能够继续运转不至于卡死,系统会尝试杀死一些不重要的进程来进行内存回收,这其中涉及的模块主要是:Linux OOM Killer和LowMemoryKiller。
Linux OOM Killer是Linux内核的一部分,其源码可以在这里查看:/mm/oom_kill.c。
Linux OOM Killer的基本想法是:
当系统已经没法再分配内存的时候,内核会遍历所有的进程,对每个进程计算badness值,得分(badness)最高的进程将会被杀死。
即:badness得分越低表示进程越重要,反之表示不重要。
Linux OOM Killer的执行流程如下:
_alloc_pages -> out_of_memory() -> select_bad_process() -> oom_badness()
这其中,_alloc_pages 是内核在分配内存时调用的函数。当内核发现无法再分配内存时,便会计算每个进程的badness值,然后选择最大的(系统认为最不重要的)将其杀死。
那么,内核是如何计算进程的badness值的呢?请看下面的代码:
unsigned long oom_badness(struct task_struct *p, struct mem_cgroup *memcg, const nodemask_t *nodemask, unsigned long totalpages) { long points; long adj; ... points = get_mm_rss(p->mm) + p->mm->nr_ptes + get_mm_counter(p->mm, MM_SWAPENTS); task_unlock(p); if (has_capability_noaudit(p, CAP_SYS_ADMIN)) points -= (points * 3) / 100; adj *= totalpages / 1000; points += adj; return points > 0 ? points : 1; }
从这段代码中,我们可以看到,影响进程badness值的因素主要有三个:
即,oom_score_adj(关于oom_score_adj,在Android系统中的进程管理:进程的优先级一文中我们专门讲解过。)值越小,进程占用的内存越小,并且如果是root用户的进程,系统就认为这个进程越重要。
反之则被认为越不重要,越容易被杀死。
OOM Killer是在系统内存使用情况非常严峻的时候才会起作用。但直到这个时候才开始杀死进程来回收内存是有点晚的。因为在进程被杀死之前,其他进程都无法再申请内存了。
因此,Google在Android上新增了一个LowMemoryKiller模块。LowMemoryKiller通常会在Linux OOM Killer工作之前,就开始杀死进程。
LowMemoryKiller的做法是:
提供6个可以设置的内存级别,当系统内存每低于一个级别时,将oom_score_adj大于某个指定值的进程全部杀死。
这么说会有些抽象,但具体看一下LowMemoryKiller的配置文件我们就好理解了。
LowMemoryKiller在sysfs上暴露了两个文件来供系统调整参数,这两个文件的路径是:
如果你手上有一个Android设备,你可以通过adb shell连上去之后,通过cat命令查看这两个文件的内容。
这两个文件是配对使用的,每个文件中都是由逗号分隔的6个整数值。
在某个设备上,这两个文件的值可能分别是下面这样:
这组配置的含义是;当系统内存低于80640k时,将oom_score_adj值大于906的进程全部杀死;当系统内存低于55296k时,将oom_score_adj值大于900的进程全部杀死,其他类推。
LowMemoryKiller杀死进程的时候会在内核留下日志,你可以通过dmesg 命令中看到。这个日志可能是这样的:
lowmemorykiller: Killing 'gnunet-service-' (service adj 0, to free 327224kB on behalf of 'kswapd0' (21) because cache 6064kB is below limit 6144kB for oom_score_adj 0
从这个日志中,我们可以看到被杀死进程的名称,进程pid和oom_score_adj值。另外还有系统在杀死这个进程之前系统内存还剩多少,以及杀死这个进程释放了多少。
LowMemoryKiller的源码也在内核中,路径是:kernel/drivers/staging/android/lowmemorykiller.c。
lowmemorykiller.c中定义了如下几个函数:
LowMemoryKiller本身是一个内核驱动程序的形式存在,lowmem_init和lowmem_exit 分别负责模块的初始化和退出清理工作。
在lowmem_init函数中,就是通过register_shrinker向内核中注册了register_shrinker 函数:
static int __init lowmem_init(void) { register_shrinker(&lowmem_shrinker); return 0; }
register_shrinker函数就是LowMemoryKiller的算法核心,这个函数的代码和说明如下:
static int lowmem_shrink(struct shrinker *s, struct shrink_control *sc) { struct task_struct *tsk; struct task_struct *selected = NULL; int rem = 0; int tasksize; int i; short min_score_adj = OOM_SCORE_ADJ_MAX + 1; int minfree = 0; int selected_tasksize = 0; short selected_oom_score_adj; int array_size = ARRAY_SIZE(lowmem_adj); int other_free = global_page_state(NR_FREE_PAGES) - totalreserve_pages; int other_file = global_page_state(NR_FILE_PAGES) - global_page_state(NR_SHMEM) - total_swapcache_pages(); if (lowmem_adj_size < array_size) array_size = lowmem_adj_size; if (lowmem_minfree_size < array_size) array_size = lowmem_minfree_size; // lowmem_minfree 和lowmem_adj 记录了两个配置文件中配置的数据 for (i = 0; i < array_size; i++) { minfree = lowmem_minfree[i]; // 确定当前系统处于低内存的第几档 if (other_free < minfree && other_file < minfree) { // 确定需要杀死的进程的oom_score_adj的上限 min_score_adj = lowmem_adj[i]; break; } } if (sc->nr_to_scan > 0) lowmem_print(3, "lowmem_shrink %lu, %x, ofree %d %d, ma %hd\n", sc->nr_to_scan, sc->gfp_mask, other_free, other_file, min_score_adj); rem = global_page_state(NR_ACTIVE_ANON) + global_page_state(NR_ACTIVE_FILE) + global_page_state(NR_INACTIVE_ANON) + global_page_state(NR_INACTIVE_FILE); if (sc->nr_to_scan <= 0 || min_score_adj == OOM_SCORE_ADJ_MAX + 1) { lowmem_print(5, "lowmem_shrink %lu, %x, return %d\n", sc->nr_to_scan, sc->gfp_mask, rem); return rem; } selected_oom_score_adj = min_score_adj; rcu_read_lock(); // 遍历所有进程 for_each_process(tsk) { struct task_struct *p; short oom_score_adj; if (tsk->flags & PF_KTHREAD) continue; p = find_lock_task_mm(tsk); if (!p) continue; if (test_tsk_thread_flag(p, TIF_MEMDIE) && time_before_eq(jiffies, lowmem_deathpending_timeout)) { task_unlock(p); rcu_read_unlock(); return 0; } oom_score_adj = p->signal->oom_score_adj; // 跳过那些oom_score_adj值比目标值小的 if (oom_score_adj < min_score_adj) { task_unlock(p); continue; } tasksize = get_mm_rss(p->mm); task_unlock(p); if (tasksize <= 0) continue; // selected 是将要杀死的备选进程 if (selected) { // 跳过那些oom_score_adj比备选的小的 if (oom_score_adj < selected_oom_score_adj) continue; // 如果oom_score_adj一样,跳过那些内存消耗更小的 if (oom_score_adj == selected_oom_score_adj && tasksize <= selected_tasksize) continue; } // 更换备选的目标,因为又发现了一个oom_score_adj更大, // 或者内存消耗更大的进程 selected = p; selected_tasksize = tasksize; selected_oom_score_adj = oom_score_adj; lowmem_print(2, "select '%s' (%d), adj %hd, size %d, to kill\n", p->comm, p->pid, oom_score_adj, tasksize); } // 已经选中目标,记录日志并杀死进程 if (selected) { long cache_size = other_file * (long)(PAGE_SIZE / 1024); long cache_limit = minfree * (long)(PAGE_SIZE / 1024); long free = other_free * (long)(PAGE_SIZE / 1024); trace_lowmemory_kill(selected, cache_size, cache_limit, free); lowmem_print(1, "Killing '%s' (%d), adj %hd,\n" \ " to free %ldkB on behalf of '%s' (%d) because\n" \ " cache %ldkB is below limit %ldkB for oom_score_adj %hd\n" \ " Free memory is %ldkB above reserved\n", selected->comm, selected->pid, selected_oom_score_adj, selected_tasksize * (long)(PAGE_SIZE / 1024), current->comm, current->pid, cache_size, cache_limit, min_score_adj, free); lowmem_deathpending_timeout = jiffies + HZ; set_tsk_thread_flag(selected, TIF_MEMDIE); send_sig(SIGKILL, selected, 0); rem -= selected_tasksize; } lowmem_print(4, "lowmem_shrink %lu, %x, return %d\n", sc->nr_to_scan, sc->gfp_mask, rem); rcu_read_unlock(); return rem; }
在任何时候,应用进程都可能死亡,例如被OOM Killer或者LowMemoryKiller杀死,自身crash死亡又或者被用户手动杀死。无论哪种情况,作为应用进程的管理者ActivityManagerService都需要知道。
在应用进程死亡之后,ActivityManagerService需要执行如下工作:
ActivityManagerService是利用Binder提供的死亡通知机制来进行进程的死亡处理的。关于Binder请参阅其他资料,限于篇幅关系,这里不再展开讲解。
简单来说,死亡通知机制就提供了进程间的一种死亡监听的能力:当目标进程死亡的时候,监听回调会执行。
ActivityManagerService中的AppDeathRecipient监听了应用进程的死亡消息,该类代码如下:
private final class AppDeathRecipient implements IBinder.DeathRecipient { final ProcessRecord mApp; final int mPid; final IApplicationThread mAppThread; AppDeathRecipient(ProcessRecord app, int pid, IApplicationThread thread) { mApp = app; mPid = pid; mAppThread = thread; } @Override public void binderDied() { synchronized(ActivityManagerService.this) { appDiedLocked(mApp, mPid, mAppThread, true); } } }
每一个应用进程在启动之后,都会attach到ActivityManagerService上通知它自己的进程已经启动完成了。这时ActivityManagerService便会为其创建一个死亡通知的监听器。在这之后如果进程死亡了,ActivityManagerService便会收到通知。
private final boolean attachApplicationLocked(IApplicationThread thread, int pid) { ... try { AppDeathRecipient adr = new AppDeathRecipient( app, pid, thread); thread.asBinder().linkToDeath(adr, 0); app.deathRecipient = adr; } catch (RemoteException e) { app.resetPackageList(mProcessStats); startProcessLocked(app, "link fail", processName); return false; } ... }
进程死亡之后的处理工作是appDiedLocked这个方法中处理的,这部分还是比较容易理解的,这里就不过多讲解了。
原文地址:
Android系统中的进程管理:内存的回收
Android系统中的进程管理:进程的创建
Android系统中的进程管理:进程的优先级