Process management of Android system (Creation->Priority->Recycling)

1. Creation of process

1 Overview

The Android system is based on the Linux kernel, so process management is naturally inseparable from the mechanisms provided by Linux itself. For example:

  • Create by fork
  • Manage processes through semaphores
  • Query and adjust process status through the proc file system

For Android, the main contents of process management include the following parts:

  • Creation of process
  • Process priority management
  • Process memory management
  • Process recycling and death handling

This article will specifically explain the creation of processes, and the rest will be explained in subsequent articles.

2. Main modules

In order to facilitate the explanation below, here are some main modules involved in process creation in the Android system.

At the same time, in order to facilitate readers to understand these modules in more detail, the code paths of these modules are also provided here.

The code path mentioned here refers to the path in the source code of AOSP.

For information on how to obtain the AOSP source code, please see here: Downloading the Source .

This article uses the Android N version code as an example. The Source Code Tags used are: android-7.0.0_r1.

Related modules :

1. app_process

Code path: frameworks/base/cmds/app_process

Note: app_process is an executable program. The main function of this program is to start the zygote and system_server processes.

2. Zygote

Code path: frameworks/base/core/java/com/android/internal/os/ZygoteInit.java

Note: The zygote process is the parent process of all application processes. This is a very important process in the system. We will explain it in detail below.

3. ActivityManager

Code path: frameworks/base/services/core/java/com/android/server/am/

Note: am is the abbreviation of ActivityManager.

The code in this directory is responsible for the management of all four major components of Android (Activity, Service, ContentProvider, BroadcastReceiver), and also controls the creation of all application processes and the priority management of processes.

Therefore, the content of this part will be the focus of this series of articles.

3. Processes and threads

This article from the official Android development website: Processes and Threads provides a very good introduction to some basic concepts and important knowledge related to processes in the Android system.

Please be sure to skim this article before reading below.

4. About the process

In the Android system, processes can be roughly divided into two categories : system processes and application processes .

System processes are built-in to the system (for example: init, zygote, system_server processes) and are an essential part of the operating system. The functions of system processes are:

  • Manage hardware devices
  • Provide basic capabilities to access the device
  • Manage application processes

The application process refers to the process in which the application runs. These applications may be shipped with the system (such as Launcher, phone, SMS, etc.), or they may be installed by users themselves (such as WeChat, Alipay, etc.).

The number of system processes is usually fixed (determined after leaving the factory or system upgrade), and system processes usually always survive and reside in memory. Abnormal exit of the system process may cause the device to be unable to be used normally.

Applications and application processes are often different on each device used. How to manage these uncertain application processes is something that the operating system itself must carefully consider. It is also one of the criteria to measure the quality of an operating system.

In this article, we will introduce three system processes: init , zygote and system_server .

In addition, this series of articles will focus on explaining how the Android system manages application processes .

5. init process

The init process is the beginning of everything. In the Android system, the process numbers of all processes are uncertain, but the process number of the init process must be 1.

Because this process must be the first process in the system.

Moreover, the init process controls the startup logic of the entire system.

We know that Android may run on a variety of different platforms and different devices. Therefore, the startup logic is not the same. In order to adapt to the needs of various platforms and devices, the initialization work of the init process is managed through the init.rc configuration file.

You can find these configuration files in the system/core/rootdir/ path of the AOSP source code.

The main entry file of the configuration file is init.rc. This file will introduce several other files through import.

In this article, we refer to these files collectively as init.rc.

init.rc is configured through Android Init Language . Readers are advised to read the syntax description briefly .

init.rc configures what should be done when the system starts and which system processes should be started.

Two particularly important processes are: zygote and system_server processes.

  • The Chinese meaning of zygote is "fertilized egg". This is a very meaningful name: all application processes are child processes forked by zygote, so the zygote process is the parent process of all application processes.
  • The system_server process is, as its name suggests, a system server. Almost all services of the Framework layer are located in this process. This includes ActivityManagerService, which manages four major components.

6. Zygote process

The init.rc file will select one of the following files to start the zygote process depending on the platform:

  • init.zygote32.rc
  • init.zygote32_64.rc
  • init.zygote64.rc
  • init.zygote64_32.rc

The contents of these files are roughly the same and are just for serving different platforms. Here we take the init.zygote32.rc file as an example to take a look at its contents:

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

In this configuration file (if you don’t understand the meaning of this configuration, please read the document: Android Init Language ), a service process named zygote is started. This process is created through the /system/bin/app_process executable program.

And when starting this executable program, the parameters -Xzygote /system/bin --zygote --start-system-server class main are passed.

To know what is done here, we need to look at the source code of app_process.

The source code of app_process is in this path: frameworks/base/cmds/app_process/app_main.cpp.

The main function of this file has the following code:

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;
    }
}

It will be judged here,

  • If you execute this command with the --zygote parameter, com.android.internal.os.ZygoteInit will be started through runtime.start.
  • If the --start-system-server parameter is included in the parameter, start-system-server will be added to args.

This code is implemented in C++. There is no Java environment when executing this code. And runtime.start is to start the Java virtual machine and start the specified class in the virtual machine. So the next logic is in ZygoteInit.java.

The main code of the main function of this file is as follows::

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;
   }
}

In this code, we mainly focus on the following lines:

  1. Register Zygote Socket through registerZygoteSocket(socketName);
  2. Preload public resources required by all applications through preload();
  3. Start system_server through startSystemServer(abiList, socketName);
  4. Wait for connection on Looper through runSelectLoop(abiList);

What needs to be explained here is: after the zygote process is started, a socket will be started and it will wait for a connection on this socket through Looper.

All application processes are created by sending data to this socket and then created by the zygote process.

Another point to note here is: In the Zygote process, public resources that all applications need are loaded through the preload function.

Preloading these public resources has the following two benefits:

  • Speed ​​up application startup because these resources are already loaded when the zygote process starts.
  • Save memory by sharing. This is a mechanism provided by Linux itself: content that has been loaded by the parent process can be shared among the child processes without multiple copies of the data (unless the child process modifies the data.)

The resources of preload are mainly some basic classes and Resource resources related to the Framework, and these resources are needed by all applications:

The API implementations called by developers to develop applications through the Android SDK are all in the 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");
}

7. system_server process

As mentioned above, after the zygote process is started, the system_server process will be started as needed.

The system_server process contains a large number of system services. For example:

  • NetworkManagementService responsible for network management
  • WindowManagerService responsible for window management
  • VibratorService responsible for vibration management
  • InputManagerService responsible for input management

etc. Regarding system_server, we will explain it specifically in other articles in the future, so we won’t explain it too much here.

In this article, we only focus on the system service ActivityManagerService in system_server.

8、ActivityManagerService

As mentioned above: the zygote process will start a socket after it is started, and then wait for the connection in this socket.

What will connect it is ActivityManagerService.

Because ActivityManagerService controls the creation of all application processes.

All application processes are created by ActivityManagerService sending requests to the Zygote process through sockets, and then created by zygote fork.

ActivityManagerService requests zygote to create a process through the Process.start method:

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);
   }
}

This function will assemble the parameters required to start the process and send them to the zygote process through the socket. Then the zygote process forks the process based on the parameters sent.

In ActivityManagerService, the place where Process.start is called is the following method:

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);
...
}

We will see below that the creation of all four major component processes is created by calling the startProcessLocked method here.

For each application process, there is a ProcessRecord corresponding to it in ActivityManagerService . This object records all detailed status of the application process.

PS: We will explain the internal structure of ProcessRecord in the next article.

For the convenience of searching, each ProcessRecord will exist in the following two collections.

  • Collection organized by name and uid :
/**
* 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 ProcessMap<ProcessRecord> mProcessNames = new ProcessMap<ProcessRecord>();
  • Collection organized by pid:
/**
* All of the processes we currently have running organized by pid.
* The keys are the pid running the application.
*
* <p>NOTE: This object is protected by its own lock, NOT the global
* activity manager lock!
*/
final SparseArray<ProcessRecord> mPidsSelfLocked = new SparseArray<ProcessRecord>();

The picture below summarizes the above content:

9. About application components

Processes and Threads mentioned:

" When an app component starts and the app is not running any other components, the Android system uses a single thread of execution to launch a new Linux process for the app. "

Therefore, any one of the four major components will lead to the creation of an application process. Below we will take a detailed look at how each of them leads to the creation of the application process when they are started.

PS: The management of the four major components itself is a relatively large topic. Due to space limitations, we will not explain it in depth here. Here we mainly explain the relationship between the four major components and process creation.

In the application, developers pass:

  • startActivity(Intent intent) to start Activity
  • startService(Intent service) to start Service
  • sendBroadcast(Intent intent) to send broadcast
  • Interface in ContentResolver to use ContentProvider

Among them, startActivity, startService and sendBroadcast also have some overloaded methods.

In fact, all the methods mentioned here are ultimately called to the ActivityManagerService through Binder and processed by it.

A special note here: the application process and the process where ActivityManagerService is located (i.e., system_server process) are independent of each other. The methods between the two processes usually cannot call each other directly.

In the Android system, the Binder framework is specially provided to provide inter-process communication and method calling capabilities.

The calling relationship is shown in the figure below:

10. Activity and process creation

In ActivityManagerService, there is an ActivityRecord object corresponding to each running Activity. This object records the detailed status of the Activity.

The startActivity method in ActivityManagerService accepts the request of Context.startActivity. The method code is as follows:

@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 startup is a very complex process. Here we briefly introduce the background knowledge:

  • Activity is managed through Stack and Task in ActivityManagerService
  • Each Activity belongs to a Task, and a Task may contain multiple Activities. A Stack contains multiple Tasks
  • The ActivityStackSupervisor class is responsible for managing all Stacks
  • The startup process of Activity will involve:
    • Intent analysis
    • Stack, Task query or creation
    • Creation of Activity process
    • Creation of Activity window
    • Activity life cycle scheduling

The management structure of Activity is shown in the figure below:

At the end of the Activity startup, the previous Activity will be paused and the newly started Activity will be resumed so that it can be seen by the user.

At this time, if it is found that the newly started Activity process has not started, it will be started through startSpecificActivityLocked. The entire calling process is as follows:

  • ActivityManagerService.activityPaused =>
  • ActivityStack.activityPausedLocked =>
  • ActivityStack.completePauseLocked =>
  • ActivityStackSupervisor.ensureActivitiesVisibleLocked =>
  • ActivityStack.makeVisibleAndRestartIfNeeded =>
  • ActivityStackSupervisor.startSpecificActivityLocked =>
  • ActivityManagerService.startProcessLocked

The key code of ActivityStackSupervisor.startSpecificActivityLocked is as follows:

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);
}

The ProcessRecord app here describes the process in which the Activity is located.

11. Service and process creation

The startup of Service is simpler than that of Activity.

In ActivityManagerService, there is a ServiceRecord object corresponding to each running Service. This object records the detailed status of the Service.

The startService method in ActivityManagerService handles the request of Context.startServiceAPI. Related code:

@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;
   }
}

The mServices object in this code is of type ActiveServices. This class is specifically responsible for managing active Services.

The calling process for starting Service is as follows:

  • ActivityManagerService.startService =>
  • ActiveServices.startServiceLocked =>
  • ActiveServices.startServiceInnerLocked =>
  • ActiveServices.bringUpServiceLocked =>
  • ActivityManagerService.startProcessLocked

ActiveServices.bringUpServiceLocked will determine if the process where the Service is located has not been started yet.

Then start it through ActivityManagerService.startProcessLocked. The relevant code is as follows:

// 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 here is ActivityManagerService.

12. Provider and process creation

In ActivityManagerService, there is a ContentProviderRecord object corresponding to each running ContentProvider. This object records the detailed status of ContentProvider.

Developers use ContentProvider through the insert, delete, update, and query APIs in ContentResolver. In the implementation of ContentResolver, no matter which interface here is used, ContentResolver will first obtain a remote interface of type IContentProvider through the acquireProvider method. This remote interface interfaces with the implementation provider of ContentProvider.

The same ContentProvider may be used by multiple modules at the same time, and the process calling the ContentResolver interface is just a client of the ContentProvider. The real ContentProvider provider runs in its own process. The communication between the two processes needs to be through the remote interface form of Binder. to call. As shown below:

ContentResolver.acquireProvider will eventually be called to ActivityManagerService.getContentProvider. The method code is as follows:

@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);
}

In the getContentProviderImpl method, it will be judged whether the corresponding ContentProvider process has been started.

If not, start it through the startProcessLocked method.

13. Receiver and process creation

Developers send broadcasts through the Context.sendBroadcast interface. The ActivityManagerService.broadcastIntent method handles the corresponding broadcast sending.

Broadcasting is a one-to-many message form, and the number of broadcast recipients is undetermined. So sending the broadcast itself can be a time-consuming process (because of the notifications one by one).

Inside ActivityManagerService, broadcasts are managed through queues:

  • BroadcastQueue describes a broadcast queue
  • BroadcastRecord describes a broadcast event

In ActivityManagerService, if a request to send a broadcast is received, a BroadcastRecord will be created first and then placed in the BroadcastQueue.

Then notify the queue to handle the broadcast by itself. Then the ActivityManagerService itself can continue to handle other requests.

The broadcast queue itself handles the sending of broadcasts in another thread, so as to ensure that the load on the main thread of ActivityManagerService will not be too heavy.

The logic of notifying broadcast events to recipients is truly implemented in the BroadcastQueue.processNextBroadcast(boolean fromMsg) method. In this method, if it is found that the receiver (ie BrodcastReceiver) has not been started, it will be started through the ActivityManagerService.startProcessLocked method. The relevant ones are as follows:

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;
   }
}

At this point, the startup of the four major components has been analyzed.

2. Priority of process

1 Introduction

The priority of a process reflects the system's determination of the importance of the process.

In the Android system, the priority of a process affects the following three factors:

  • When memory is tight, the system’s recycling strategy for processes
  • The system’s CPU scheduling strategy for processes
  • The memory allocation and garbage collection strategy of the virtual machine for the process

This article will mainly explain the system’s basis for judging process priority and its calculation method.

In the article Processes and Threads (if you haven't read it yet, please read this article now ), we have learned that the system has the following five categories for the priority of processes:

  1. foreground process
  2. visible process
  3. service process
  4. backstage process
  5. Empty process

In fact this is just a rough division. In the internal implementation of the system, there are far more than five priorities.

2. Basis for priority

Let’s briefly list the relevant information about application components and processes:

  • Every Android application process may contain one or more of the four major components.
  • For running Services and ContentProviders, there may be several client processes using them.
  • The application process is created by ActivityManagerService by sending a request to zygote, and there is a ProcessRecord object corresponding to each running process in ActivityManagerService.

A simplified diagram of ProcessRecord is as follows:

In ProcessRecord, the relevant information of the application components is recorded in detail. The relevant code is as follows:

// all activities running in the process
final ArrayList<ActivityRecord> activities = new ArrayList<>();
// all ServiceRecord running in this process
final ArraySet<ServiceRecord> services = new ArraySet<>();
// services that are currently executing code (need to remain foreground).
final ArraySet<ServiceRecord> executingServices = new ArraySet<>();
// All ConnectionRecord this process holds
final ArraySet<ConnectionRecord> connections = new ArraySet<>();
// all IIntentReceivers that are registered from this process.
final ArraySet<ReceiverList> receivers = new ArraySet<>();
// class (String) -> ContentProviderRecord
final ArrayMap<String, ContentProviderRecord> pubProviders = new ArrayMap<>();
// All ContentProviderRecord process is using
final ArrayList<ContentProviderConnection> conProviders = new ArrayList<>();

here:

  • activities records the activities running in the process
  • services, executingServices records the Services running in the process
  • receivers records the BroadcastReceiver running in the process
  • pubProviders records the ContentProvider running in the process

and:

  • connections records the Service connections
  • conProviders records the connection to ContentProvider

Connection is the record of client usage status. It is similar to Service and ContentProvider. Each client needs to record a connection. The significance of the connection is that the process priority of the connected client will affect the priority of the process where the used Service and ContentProvider are located. For example: when a background Service is being used by a foreground Activity, then the background Service needs to set a higher priority so that it will not be recycled. (Otherwise, once the background Service process is recycled, it will affect the foreground Activity.)

The status of all these components is the decisive factor in the priority of the process in which they are located. The status of a component refers to:

  • Whether the activity is in the foreground and visible to the user
  • Which clients are using the Service?
  • Which clients are using the ContentProvider?
  • Whether BroadcastReceiver is accepting broadcasts

3. Basis of priority

3.1 oom_score_adj

For each running process, the Linux kernel exposes such a file through the proc file system to allow other programs to modify the priority of the specified process:

/proc/[pid]/oom_score_adj . (Modifying this file requires root permissions)

The range of values ​​allowed by this file is: -1000 ~ +1000. The smaller the value, the more important the process is .

When the memory is very tight, the system will traverse all processes to determine which process needs to be killed to reclaim memory. At this time, the value of the oom_score_adj file will be read. Regarding the use of this value, we will explain in detail when we explain process recycling later.

PS: In versions before Linux 2.6.36, the file provided by Linux for adjusting priority is /proc/[pid]/oom_adj. The allowed value range of this file is between -17 ~ +15. The smaller the number, the more important the process is. This file has been obsolete in new versions of Linux.

But you can still use this file. When you modify this file, the kernel will directly perform the conversion and reflect the result to the oom_score_adj file.

The implementation of early versions of Android also relied on the oom_adj file. But in the new version, we have switched to using the oom_score_adj file.

The following properties in ProcessRecord reflect the value of 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 specifies the maximum value of oom_score_adj allowed by the process. This attribute is mainly used for system applications and memory-resident processes. The priority calculation method of these processes is different from that of application processes. By setting maxAdj, it is ensured that these processes always have a higher priority (later" We will see the use of this property in "Priority Algorithm").

In addition to this, there are four attributes.

Among them, the curXXX group records the results of this priority calculation. After the calculation is completed, curXXX will be copied to the corresponding setXXX group for backup. (Other properties below will also see the form of curXXX and setXXX, and the principle here is the same.)

In addition, xxxRawAdj records unrestricted adj values. "Unrestricted" means that the values ​​may exceed the range allowed by the oom_score_adj file (-1000 ~ 1000).

In order to facilitate management, the possible values ​​of oom_score_adj are predefined in ProcessList.java.

In fact, the predefined values ​​here are also a classification of application processes. They are:

static final int UNKNOWN_ADJ = 1001; // Unknown process 
static final int PREVIOUS_APP_ADJ = 700; // Previous application 
static final int HOME_APP_ADJ = 600; // Desktop process 
static final int SERVICE_ADJ = 500; // Process containing Service 
static final int HEAVY_WEIGHT_APP_ADJ = 400; // Heavyweight process 
static final int BACKUP_APP_ADJ = 300; // Backup application process 
static final int PERCEPTIBLE_APP_ADJ = 200; // Perceptible process 
static final int VISIBLE_APP_ADJ = 100; // Visible process 
static final int VISIBLE_APP_LAYER_MAX = PERCEPTIBLE_APP_ADJ - VISIBLE_APP_ADJ - 1; 
static final int FOREGROUND_APP_ADJ = 0; // Foreground process 
static final int PERSISTENT_SERVICE_ADJ = -700; // Resident service process 
static final int PERSISTENT_PROC_ADJ = -800; // Resident application process 
static final int SYSTEM_ADJ = -900; // system process
static final int NATIVE_ADJ = -1000; // native system process

Here we see that FOREGROUND_APP_ADJ = 0, this is the priority of the foreground application process. These are the applications that the user is interacting with. They are important and the system should not recycle them.

FOREGROUND_APP_ADJ = 0 is the highest priority that ordinary applications can obtain.

The priorities of VISIBLE_APP_ADJ, PERCEPTIBLE_APP_ADJ, and PREVIOUS_APP_ADJ are gradually reduced.

VISIBLE_APP_ADJ is the priority of the visible Activity process: at the same time, not necessarily only one Activity is visible. If the foreground Activity sets the transparent attribute, then the Activity behind it is also visible.

PERCEPTIBLE_APP_ADJ refers to the process that the user can perceive. The processes that can be perceived include:

  • The process contains activities that are in the pause state or are being paused.
  • The process contains the stopping Activity
  • The process contains the foreground Service

In addition, PREVIOUS_APP_ADJ describes the priority of the previous application. The so-called "previous application" means: when starting a new Activity, if the newly started Activity belongs to a new process, then the process of the current Activity that is about to be stopped will become the "previous application" process.

The heavyweight processes described by HEAVY_WEIGHT_APP_ADJ refer to those application processes that indicate through Manifest that the state cannot be saved.

In addition, in the Android system, there are some system applications that are resident in memory. These applications are usually part of the system implementation. If they do not exist, the system will be in a strange state. For example, SystemUI (status bar, Keyguard are all in this in application).

So their priority is higher than the priority of all application processes: PERSISTENT_SERVICE_ADJ = -700, PERSISTENT_PROC_ADJ = -800.

In addition, there are some implementations of system services. If these system services do not exist, the system will not work, so these applications have the highest priority and need to exist at almost any time: SYSTEM_ADJ = -900, NATIVE_ADJ = -1000.

3.2 Schedule Group

The kernel is responsible for the CPU scheduling of the process, and all running processes are not equally able to obtain equal time slices. In ProcessRecord, the scheduling group of the process is recorded through Schedule Group:

int curSchedGroup;          // Currently desired scheduling class
int setSchedGroup;          // Last set to background scheduling class

Their possible values ​​are defined in 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;

3.3 Process State

The status of the process will affect the virtual machine's memory allocation and garbage collection strategy for the process. The following attributes in ProcessRecord record the status of the process:

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

The possible values ​​of these properties are defined in ActivityManager, and the names of these constants already describe their functions:

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;

4. Priority update

As mentioned earlier, the system will set different priorities for processes in different states. But in fact, the status of the process is always changing. For example: the user can start a new Activity at any time, or switch a foreground Activity to the background. At this time, the priority of the process of the Activity whose status changes needs to be updated.

Moreover, Activity may use other Services or ContentProvider. When the activity's process priority changes, the priority of the Service or ContentProvider it uses should also change.

There are the following two methods in ActivityManagerService for updating the priority of the process:

  • final boolean updateOomAdjLocked(ProcessRecord app)
  • final void updateOomAdjLocked()

The first method is to update the priority for a specified individual process. The second is to update the priorities of all processes.

In the following situations, the priority of the specified application process needs to be updated:

  • When a new process starts to use the ContentProvider in this process
  • When a Service in this process is bound or unbind by other processes
  • When the execution of the Service in this process is completed or exits
  • When a BroadcastReceiver in this process is receiving broadcasts
  • When the BackUpAgent in this process starts or exits

The relationship between final boolean updateOomAdjLocked(ProcessRecord app) being called is shown in the figure below:

In some cases, the system needs to update the priorities of all application processes, such as:

  • When a new process is started
  • When a process exits
  • When the system is cleaning up background processes
  • When a process is marked as a foreground process
  • When a process enters or exits the cached state
  • When the system locks or unlocks the screen
  • When an Activity starts or exits
  • When the system is processing a broadcast event
  • When the foreground Activity changes
  • When a Service starts

The relationship diagram where final void updateOomAdjLocked() is called is as follows:

5. Priority algorithm

The computeOomAdjLocked method in ActivityManagerService is responsible for calculating the priority of the process. This method totals about 700 lines. The execution process mainly includes the following 10 steps:

Let’s look at each of these steps in detail:

  • 1. Confirm whether the process is an empty process. There are no components in the empty process, so the main thread is also null (ProcessRecord.thread describes the main thread of the application process). If it is an empty process, there is no need to do subsequent calculations. Just set it directly to the ProcessList.CACHED_APP_MAX_ADJ level.
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);
}
  • 2. Confirm whether maxAdj is set. As mentioned above, the system process or Persistent process will maintain its higher priority by setting maxAdj. For such processes, it is not necessary to calculate according to the algorithm of ordinary processes, but directly according to the value of maxAdj. Just set it up.
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);
  }
  • 3. Confirm whether there are components with foreground priority in the process. The components with foreground priority refer to: a. Activity in the foreground; b. Receiver that is receiving broadcast; c. Service that is executing tasks; Note: In addition, there are Instrumentation is considered to have higher priority. The Instrumentation application is used to assist testing and does not need to be considered in a normally operating system. Assuming that the process contains any of the foreground priority components mentioned above, just set the process priority to FOREGROUND_APP_ADJ directly. Because this is already the highest priority the application can obtain.
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";
  }
  • 4. Confirm whether there is a higher priority Activity in the process. Here you need to traverse all the Activities in the process and find out which one has the highest priority and set it as the priority of the process. Even if the Activity is not a foreground Activity, the priority of the Activity in the following states is considered to be a higher priority:
    1. The activity is visible
    2. The Activity is in the Pause state.
    3. The Activity is stopping
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; 
 } 
}
  • 5. Confirm whether there is a foreground service in the process. The service started through startForeground is considered a foreground service. Give such processes PERCEPTIBLE_APP_ADJ level priority.
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;
 }
}
  • 6. Confirm whether it is a special type of process. Special types of processes include: heavyweight processes, desktop processes, previous application processes, and processes that are performing backups. The "heavyweight process" and "previous application" process have been mentioned above. The desktop refers to the Launcher on Android.
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;
 }
}
  • 7. Calculate priorities based on the clients of all Services. Here you need to traverse all Services, and you also need to traverse all connections of each Service. Then confirm the priority of the client process based on the connection relationship to determine the priority of the current process. ConnectionRecord.binding.client is the client process ProcessRecord, so you can know the priority of the client process.
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--) {
     ArrayList<ConnectionRecord> clist = 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);
  • 8. According to the client confirmation priority of all Providers, this is similar to the Service. All Providers need to be traversed, as well as all connections of each Provider. Then confirm the priority of the client process based on the connection relationship to determine the priority of the current process. Similarly, ContentProviderConnection.client is the ProcessRecord of the client process.
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);
     ...
  • 9. Finishing work The finishing work is mainly to do some processing based on some special status of the Service and Provider in the process. In addition, it also does some processing for the empty process and the process with maxAdj set. The code will not be posted here.

What I want to specifically explain here is that in this step, the Service process will also be distinguished as ServiceB. The system divides the Service process into ServiceA and ServiceB. ServiceA is a relatively new Service, while ServiceB is relatively "old" and may not be of interest to users, so ServiceB's priority will be relatively low.

static final int SERVICE_B_ADJ = 800;
static final int SERVICE_ADJ = 500;

The standard for ServiceB is: app.serviceb = mNewNumAServiceProcs > (mNumServiceProcs/3); That is: the first 1/3 of all Service processes are ServiceA, and the rest are 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;
  • 10. Save the results. Finally, you need to save the calculation results to ProcessRecord:
app.curAdj = app.modifyRawOomAdj(adj);
app.curSchedGroup = schedGroup;
app.curProcState = procState;
app.foregroundActivities = foregroundActivities;

6. Effectiveness of priority

The effectiveness of the priority refers to actually applying the calculated priority to the system. The applyOomAdjLocked method is responsible for this work.

We mentioned earlier that priority means three aspects, and the effect here corresponds to these three aspects:

  1. ProcessList.setOomAdj(app.pid, app.info.uid, app.curAdj); Write the calculated adj value into procfs, that is: /proc/[pid]/oom_score_adj file.
  2. Process.setProcessGroup(app.pid, processGroup); is used to set the scheduling group of the process.
  3. app.thread.setProcessState(app.repProcState); This method will eventually call VMRuntime.getRuntime().updateProcessState(); to set the status of the process to the virtual machine.

7. Conclusion

We mentioned in the preface that "priority reflects the system's determination of the importance of a process."

Then, how the system evaluates the priority of the process is a very important feature of the system itself. Understanding this characteristic of the system is very meaningful for us to develop applications and analyze the behavior of application running.

When the system determines priorities, it should be fair and impartial and should not allow developers to take advantage of it.

"Fairness and justice" means that the system needs to stand in a middleman state, without biasing any application, and fairly allocate system resources to the processes that really need it. And when system resources are tight, unimportant processes are recycled.

Through the above analysis, we can see that there are three main categories of processes that the Android system considers "important":

  1. System process
  2. The process of interaction between the foreground and the user
  3. The process used by the foreground process

However, there is room for improvement in this regard. For example, analysis of user habits can be introduced: if it is an application that is frequently used by users, these applications can be given a higher priority to improve the response speed of these applications. At present, some domestic Android customization manufacturers have begun to support such functions.

"Do not allow developers to take advantage of it" means that the system's factors for determining process priority should not be exploited by developers. Because once developers can take advantage of it, each developer will definitely set their own as a high priority to seize more resources.

It should be noted that Android has flaws in this aspect: on the Android system, the priority of the foreground can be obtained through startForeground . Later, Google also realized this problem, so in versions above API Level 18, calling the startForeground API will display a notification in the notification bar to inform the user. However, this improvement has bugs : developers can start two Services through startForeground at the same time, specify the same notification ID, and then exit one of them. In this way, the application will not display the notification icon in the notification bar and get the priority of the foreground. . This is an opportunity for developers to take advantage of.

3. Memory recycling

1 Introduction

Memory is a very precious resource in the system. Even if the memory on today's mobile devices has reached the 4G or even 6G level, memory recycling is still important, because on the Android system, there may be dozens of processes running at the same time. Even hundreds of them.

How to reasonably allocate system memory to each process and how to recycle memory is one of the issues that the operating system needs to deal with.

This article will explain the knowledge related to memory recycling in the Android system.

For memory recycling, it can be mainly divided into two levels:

  • In-process memory recycling : memory recycling by releasing resources in the process
  • Process-level memory recycling : memory recycling by killing processes

Among them, in-process memory recycling is mainly divided into two aspects:

  • The garbage collection mechanism of the virtual machine itself
  • When the system memory status changes, the application is notified and the developer is allowed to recycle the memory.

Process-level memory recycling mainly relies on two modules in the system, which are:

  • Linux OOM Killer
  • LowMemoryKiller

In certain scenarios, they will kill the process to recycle memory.

The following figure describes several memory reclamation mechanisms:

2. Introduction to memory management of Android system

In the Android system, processes can be roughly divided into two categories : system processes and application processes .

System processes are built-in to the system (for example: init, zygote, system_server processes) and are an essential part of the operating system. The functions of system processes are:

  • Manage hardware devices
  • Provide basic capabilities to access the device
  • Manage application processes

The application process refers to the process in which the application runs. These applications may be shipped with the system (such as Launcher, phone, SMS, etc.), or they may be installed by users themselves (such as WeChat, Alipay, etc.).

Application processes in Android usually run in the Java virtual machine. In versions before Android 5.0, this virtual machine was Dalvik. In versions 5.0 and later, Android introduced a new virtual machine called Android Runtime, or "ART" for short.

For information about ART and Dalvik, see here: ART and Dalvik . Both Dalvik and ART have the ability to collect garbage. We will explain this later.

Android applications will rely on some public resources, such as classes and interfaces provided by the Android SDK, as well as images and strings exposed by the Framework. In order to save memory, these resources are not copied separately in memory for each application process. Instead, it will be shared among all applications, because all application processes are child processes forked out of the Zygote process. Regarding this part, we have already explained it in the article Process Management in the Android System: Process Creation .

In the Java language, objects created through new will allocate memory in the heap. The application heap size is limited. The system will determine the memory size allowed to be used by each application based on the physical memory size of the device. Once the memory used by the application exceeds this size, an OutOfMemoryError will occur .

Therefore, developers need to pay attention to the memory usage of the application. On how to monitor your application's memory usage, see here: Investigating Your RAM Usage .

3. Developer-related APIs

Below are some memory-related developer APIs that are part of the Android SDK.

3.1 ComponentCallbacks2

The Android system will notify the application based on the current system memory status and the application's own status. The purpose of this kind of notification is to hope that the application can sense the status changes of the system and itself, so that developers can more accurately grasp the operation of the application.

For example: when the system memory is sufficient, the application can cache more resources in order to improve response performance. But when system memory is tight, developers should release certain resources to alleviate the memory crunch.

The void onTrimMemory(int level) callback function in the ComponentCallbacks2 interface is used to receive this notification. Regarding this, we will explain it in detail in the section "Memory Recycling for Developers".

3.2 ActivityManager

ActivityManager, as you can see from the name, this class is a system service used to manage Activity. But this class also contains many interfaces for runtime status query, including several related to memory:

  • int getMemoryClass () Gets the memory size limit of a single application on the current device, the unit is M. Note that the return value of this function is only an approximate value.
  • void getMemoryInfo (ActivityManager.MemoryInfo outInfo) Gets the memory information of the system. For the specific structure, you can view ActivityManager.MemoryInfo . What developers may be most concerned about is availMem and totalMem.
  • void getMyMemoryState (ActivityManager.RunningAppProcessInfo outState) Gets the memory information of the calling process
  • MemoryInfo[] getProcessMemoryInfo (int[] pids) Gets the memory information of the specified process through pid
  • boolean isLowRamDevice() queries whether the current device is a low memory device

3.3 Runtime

Java applications will have an instance of the Runtime interface. Through this instance, some runtime status can be queried. The memory-related interfaces are:

  • freeMemory() obtains the remaining memory of the Java virtual machine
  • maxMemory() obtains the maximum memory that the Java virtual machine can use
  • totalMemory() gets the maximum memory owned by the Java virtual machine

4. Garbage collection of virtual machines

Garbage collection means: the virtual machine monitors the object creation and use of the application, and destroys useless objects at certain times to reclaim memory.

The basic idea of ​​garbage collection is to find out which objects in the virtual machine are no longer used and then release them. The most commonly used algorithms are the following two:

Reference counting algorithm

The reference counting algorithm maintains a number of references for each object: the initial reference count when the object is first created is 0. Each time it is referenced by an object, the reference count increases by 1, and vice versa decreases by 1. When an object's reference count returns to 0, it can be considered unused, and these objects can be garbage collected.

Readers may immediately think of how to calculate the reference count when two objects refer to each other. This part of the content will not be explained here. Interested readers can check Google or Wikipedia: Garbage collection

4.1 Object tracking algorithm

The object tracking algorithm starts from GC root type objects, tracks all objects referenced by these objects, and continues to track along these referenced objects. During the tracking process, all tracked objects are marked mark.

The remaining objects that have not been marked can be considered to be unused, so these objects can be released.

There are four types of GC root type objects mentioned here:

  • Local variables in the stack, that is, local variables in the method
  • Active threads (such as the main thread or developer-created threads)
  • static variable
  • References in JNI

The picture below describes this algorithm:

a) indicates that at the beginning of the algorithm, all objects are marked as false, and then tracking and marking starts from the GC root. The tracked objects in b) are marked. The remaining unmarked objects can be released. After the algorithm ends, all the marks of all objects in c) are set to false. In the next round of calculation, tracing starts again with the GC root.

The Dalvik virtual machine uses the object tracking algorithm. Here is its source code: MarkSweep.cpp

5. Memory recycling for developers

Memory recycling is not just a matter of the system. As a developer, you also need to release memory on appropriate occasions. Uncontrolled memory consumption will cause the application to get OutOfMemoryError .

As mentioned above, the virtual machine's garbage collection will recycle objects that will no longer be used. Therefore, all developers need to do is: when it is determined that certain objects will no longer be used, they must actively release references to them so that the virtual machine can recycle them. For objects that are no longer used, references to them are still maintained and cannot be released, which will lead to memory leaks.

In order to better perform memory recycling, the system will notify the application in some scenarios, hoping that the application can cooperate to release some memory.

The void onTrimMemory(int level) callback in the ComponentCallbacks2 interface is used to receive this event.

Activity, Service, ContentProvider and Application all implement this interface, so subclasses of these classes can receive this event.

The parameter of the onTrimMemory callback is a level. The system will send different levels according to the status of the application itself and the memory status of the system. Specifically, they include:

  • Levels that an application in Runnig state may receive
    • TRIM_MEMORY_RUNNING_MODERATE indicates that the system memory is already slightly low
    • TRIM_MEMORY_RUNNING_LOW indicates that the system memory is already quite low
    • TRIM_MEMORY_RUNNING_CRITICAL indicates that the system memory is very low and your application should consider releasing some resources.
  • The level received when the app's visibility changes
    • TRIM_MEMORY_UI_HIDDEN indicates that the application is already invisible and you can consider releasing some display-related resources.
  • Levels that an app may receive while it is in the background
    • TRIM_MEMORY_BACKGROUND means that the system memory is slightly low and your application is unlikely to be killed. But you can consider releasing resources appropriately
    • TRIM_MEMORY_MODERATE indicates that the system memory is already low. When the memory continues to decrease, your application may be killed.
    • TRIM_MEMORY_COMPLETE means that the system memory is very low and your application is about to be killed. Please release all possible resources.

Here is sample code implementing this method: Release memory in response to events

As we mentioned in the previous article: ActivityManagerService is responsible for managing all application processes.

The notification here also comes from ActivityManagerService. When updatingOomAdjLocked, ActivityManagerService will send notifications to the application through app.thread.scheduleTrimMemory based on the system memory and application status.

The app here is ProcessRecord, which is an object that describes the application process, and thread is the main thread of the application. ScheduleTrimMemory sends messages to the application process through Binder IPC. These contents have been introduced in previous articles. If you feel unfamiliar, you can read the first two articles.

In ActivityThread (this is the main thread of the application), after receiving the notification, it will traverse all the components in the application process that can accept the notification, and then call back the notification one by one.

The relevant code is as follows:

final void handleTrimMemory(int level) {
   if (DEBUG_MEMORY_TRIM) Slog.v(TAG, "Trimming memory to level: " + level);

   ArrayList<ComponentCallbacks2> callbacks = collectComponentCallbacks(true, null);

   final int N = callbacks.size();
   for (int i = 0; i < N; i++) {
       callbacks.get(i).onTrimMemory(level);
   }

   WindowManagerGlobal.getInstance().trimMemory(level);
}

6、Linux OOM Killer

The previously mentioned mechanisms all perform memory recycling by releasing objects within the process.

In fact, the number of processes running in the system and the memory consumed by each process are uncertain.

In extreme cases, the system's memory may be in a very critical state. If all processes are unwilling to release memory at this time, the system will freeze.

In order to allow the system to continue running without getting stuck, the system will try to kill some unimportant processes to recycle memory. The modules involved are mainly: Linux OOM Killer and LowMemoryKiller.

Linux OOM Killer is part of the Linux kernel, and its source code can be viewed here: /mm/oom_kill.c .

The basic idea of ​​Linux OOM Killer is:

When the system can no longer allocate memory, the kernel will traverse all processes, calculate the badness value for each process, and the process with the highest score (badness) will be killed .

That is: the lower the badness score, the more important the process is, and vice versa.

The execution process of Linux OOM Killer is as follows:

_alloc_pages -> out_of_memory() -> select_bad_process() -> oom_badness()

Among them, _alloc_pages is the function called by the kernel when allocating memory. When the kernel finds that it can no longer allocate memory, it calculates the badness value of each process and then selects the largest one (the one considered least important by the system) to kill it.

So, how does the kernel calculate the badness value of a process? Please look at the code below:

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;
}

From this code, we can see that there are three main factors that affect the badness value of the process:

  • The oom_score_adj value of the process
  • The memory footprint of the process
  • Whether the process is a process of the root user

That is, oom_score_adj (About oom_score_adj, we have specifically explained it in the article Process Management in the Android System: Priority of Processes .) The smaller the value, the smaller the memory occupied by the process, and if it is a process of the root user, the system considers this The process is more important.

On the contrary, the less important it is, the easier it is to be killed.

7、LowMemoryKiller

OOM Killer will only work when the system memory usage is very severe. But it's a little late to start killing processes to reclaim memory until this point. Because before the process is killed, other processes can no longer apply for memory.

Therefore, Google added a new LowMemoryKiller module on Android. LowMemoryKiller usually starts killing processes before the Linux OOM Killer can work.

What LowMemoryKiller does is:

Provides 6 memory levels that can be set. When the system memory falls below one level, all processes with oom_score_adj greater than a specified value will be killed.

It would be a bit abstract to say this, but we can understand it better if we look at the configuration file of LowMemoryKiller.

LowMemoryKiller exposes two files on sysfs for the system to adjust parameters. The paths of these two files are:

  • /sys/module/lowmemorykiller/parameters/minfree
  • /sys/module/lowmemorykiller/parameters/adj

If you have an Android device, you can connect to it through adb shell and use the cat command to view the contents of these two files.

These two files are used in pairs, each file contains 6 integer values ​​separated by commas.

On a certain device, the values ​​of these two files may be as follows:

  • 18432,23040,27648,32256,55296,80640
  • 0,100,200,300,900,906

The meaning of this set of configurations is; when the system memory is lower than 80640k, kill all processes with oom_score_adj value greater than 906; when the system memory is lower than 55296k, kill all processes with oom_score_adj value greater than 900, and so on.

When LowMemoryKiller kills a process, it will leave a log in the kernel, which you can see through the dmesg command. The log may look like this:

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

From this log, we can see the name of the killed process, process pid and oom_score_adj value. In addition, there is how much system memory is left before the system kills the process, and how much is released by killing the process.

The source code of LowMemoryKiller is also in the kernel, and the path is: kernel/drivers/staging/android/lowmemorykiller.c.

The following functions are defined in lowmemorykiller.c:

  • lowmem_shrink
  • lowmem_heat
  • lowmem_exit
  • lowmem_oom_adj_to_oom_score_adj
  • lowmem_autodetect_oom_adj_values
  • lowmem_adj_array_set
  • lowmem_adj_array_get
  • lowmem_adj_array_free

LowMemoryKiller itself exists in the form of a kernel driver. lowmem_init and lowmem_exit are responsible for module initialization and exit cleanup respectively.

In the lowmem_init function, the register_shrinker function is registered with the kernel through register_shrinker:

static int __init lowmem_init(void)
{
	register_shrinker(&lowmem_shrinker);
	return 0;
}

The register_shrinker function is the core algorithm of LowMemoryKiller. The code and description of this function are as follows:

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 and lowmem_adj record the data configured in the two configuration files 
	for (i = 0; i < array_size; i++) { 
		minfree = lowmem_minfree[i]; 
		// Determine the current system Which level of low memory is it 
		if (other_free < minfree && other_file < minfree) { 
		   // Determine the upper limit of oom_score_adj of the process that needs to be killed 
			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; 
		// Skip those oom_score_adj values ​​smaller than the target value 
		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 is the candidate process to be killed 
		if (selected) { 
		   // Skip those with oom_score_adj smaller than the alternative 
			if (oom_score_adj < selected_oom_score_adj) 
				continue; 
		   // If oom_score_adj is the same, skip those with smaller memory consumption
			if (oom_score_adj == selected_oom_score_adj && 
			    tasksize <= selected_tasksize) 
				continue; 
		} 
		// Change the alternative target because another 
		process with a larger oom_score_adj, // or a larger memory consumption, is found 
		. 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); 
	} 

	// The target has been selected, log and kill the process 
	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;
}

8. Process death handling

At any time, the application process may die, such as being killed by OOM Killer or LowMemoryKiller, crashing itself, or being killed manually by the user. In either case, ActivityManagerService, as the manager of the application process, needs to know.

After the application process dies, ActivityManagerService needs to perform the following work:

  • To perform the cleanup work, the ProcessRecord inside the ActivityManagerService and the related structures of the four major components that may exist need to be cleaned up.
  • Recalculate the priority of the process As mentioned above, the priority of the process is related. If one of the processes dies, it may affect the priorities of other processes and need to be adjusted.

ActivityManagerService uses the death notification mechanism provided by Binder to handle the death of the process. Please refer to other information about Binder. Due to space limitations, I will not explain it here.

Simply put, the death notification mechanism provides a death monitoring capability between processes: when the target process dies, the monitoring callback will be executed.

The AppDeathRecipient in ActivityManagerService monitors the death message of the application process. The code for this class is as follows:

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);
       }
   }
}

After each application process is started, it will attach to ActivityManagerService to notify it that its process has been started. At this time, ActivityManagerService will create a death notification listener for it. If the process dies after this, ActivityManagerService will be notified.

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;
        }
    ...
}

The processing work after the process dies is handled by the appDiedLocked method. This part is relatively easy to understand, so I won’t explain it in detail here.

Original address:

Process management in Android system: memory recycling

Process management in the Android system: Creation of processes

Process management in Android system: priority of process

Guess you like

Origin blog.csdn.net/weixin_47465999/article/details/132043377