What is OOM?

The full name Of OOM is out-of-memory, which is a kind Of “alternative” Crash caused by iOS Jetsam mechanism. It is different from normal Crash. Crash monitoring schemes such as Signal capture cannot capture OOM events.

Why does OOM happen?

There are currently two possible causes of OOM,

  1. The overall memory usage of the system is high, and the system kills apps with lower priorities based on priority
  2. The current App reaches the “high water mark”, that is, the system reaches the memory limit for a single App, and the system will Kill you

Verification Scheme 1:

XNU opensource.apple.com/source/xnu/… , opensource.apple.com/source/xnu/… We can use these macros and functions under root to obtain the OOM memory threshold of all apps in the current state. Based on PID, we can even modify the process memory threshold to increase the OOM memory threshold.

The most useful information for us is as follows:

Typedef struct memorystatus_priority_entry {pid_t pid; typedef struct memorystatus_priority_entry; int32_t priority; uint64_t user_data; int32_tlimit; uint32_t state; } memorystatus_priority_entry_t; // You can query memory thresholds and modify memory thresholds based on the following macros#define MEMORYSTATUS_CMD_GET_PRIORITY_LIST 1
#define MEMORYSTATUS_CMD_SET_PRIORITY_PROPERTIES 2
#define MEMORYSTATUS_CMD_GET_JETSAM_SNAPSHOT 3
#define MEMORYSTATUS_CMD_GET_PRESSURE_STATUS 4
#define MEMORYSTATUS_CMD_SET_JETSAM_HIGH_WATER_MARK 5 /* Set active memory limit = inactive memory limit, both non-fatal */
#define MEMORYSTATUS_CMD_SET_JETSAM_TASK_LIMIT 6 /* Set active memory limit = inactive memory limit, both fatal */
#define MEMORYSTATUS_CMD_SET_MEMLIMIT_PROPERTIES 7 /* Set memory limits plus attributes independently */
#define MEMORYSTATUS_CMD_GET_MEMLIMIT_PROPERTIES 8 /* Get memory limits plus attributes */
#define MEMORYSTATUS_CMD_PRIVILEGED_LISTENER_ENABLE 9 /* Set the task's status as a privileged listener w.r.t memory notifications */
#define MEMORYSTATUS_CMD_PRIVILEGED_LISTENER_DISABLE 10 /* Reset the task's status as a privileged listener w.r.t memory  notifications */
/* Commands that act on a group of processes */
#define MEMORYSTATUS_CMD_GRP_SET_PROPERTIES 100

Copy the code

We can create a program with the following code

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include "kern_memorystatus.h"

#define NUM_ENTRIES 1024

char *state_to_text(int State)
{
    // Convert kMemoryStatus constants to a textual representation

    static char returned[80];

    sprintf (returned, "0x%02x ",State);

    if (State & kMemorystatusSuspended) strcat(returned,"Suspended,");

    if (State & kMemorystatusFrozen) strcat(returned,"Frozen,");

    if (State & kMemorystatusWasThawed) strcat(returned,"WasThawed,");

    if (State & kMemorystatusTracked) strcat(returned,"Tracked,");

    if (State & kMemorystatusSupportsIdleExit) strcat(returned,"IdleExit,");

    if (State & kMemorystatusDirty) strcat(returned,"Dirty,");

    if (returned[strlen(returned) -1] == ', ')

        returned[strlen(returned) -1] = '\ 0';

    return (returned);
}

int main (int argc, char **argv)
{
    struct memorystatus_priority_entry memstatus[NUM_ENTRIES];

    size_t  count = sizeof(struct memorystatus_priority_entry) * NUM_ENTRIES;

    // call memorystatus_control

    int rc = memorystatus_control (MEMORYSTATUS_CMD_GET_PRIORITY_LIST,    // 1 - only supported command on OS X

                                   0,    // pid

                                   0,    // flags

                                   memstatus, // buffer

                                   count); // buffersize

    if (rc < 0) { perror ("memorystatus_control"); exit(rc); } int entry = 0;for (; rc > 0; rc -= sizeof(struct memorystatus_priority_entry))
    {
        printf ("PID: %5d\tPriority:%2d\tUser Data: %llx\tLimit:%2d\tState:%s\n", memstatus[entry].pid, memstatus[entry].priority, memstatus[entry].user_data, memstatus[entry].limit, state_to_text(memstatus[entry].state)); entry++; }}Copy the code

Then inject the program into the jailbroken device (5s, iOS 9.1 test environment at that time) by using the Command line Tool provided by MonekyDev, connect to the device through SSH, and then run the program through the terminal. You can get information about dump. As follows:

PID:  9967	Priority: 3	User Data: 0	Limit: 6	State:0x38 Tracked,IdleExit,Dirty
PID: 11151	Priority: 3	User Data: 0	Limit: 6	State:0x38 Tracked,IdleExit,Dirty
PID: 11154	Priority: 3	User Data: 0	Limit:10	State:0x38 Tracked,IdleExit,Dirty
PID: 11165	Priority: 3	User Data: 0	Limit: 6	State:0x38 Tracked,IdleExit,Dirty
PID: 11499	Priority: 3	User Data: 0	Limit:18	State:0x28 Tracked,Dirty
PID: 10039	Priority: 4	User Data: 2100	Limit:108	State:0x00
PID:  9981	Priority: 7	User Data: 0	Limit:10	State:0x08 Tracked
PID:  9977	Priority: 7	User Data: 0	Limit:20	State:0x08 Tracked
PID:  9979	Priority: 7	User Data: 0	Limit:25	State:0x38 Tracked,IdleExit,Dirty
PID: 10021	Priority: 7	User Data: 0	Limit: 6	State:0x08 Tracked
PID: 11575	Priority:10	User Data: 10100	Limit:650	State:0x00
PID:   103	Priority:11	User Data: 0	Limit:96	State:0x08 Tracked
PID: 11442	Priority:11	User Data: 0	Limit:38	State:0x08 Tracked
PID:    67	Priority:12	User Data: 0	Limit:24	State:0x28 Tracked,Dirty
PID:    31	Priority:14	User Data: 0	Limit:650	State:0x08 Tracked
PID:    45	Priority:14	User Data: 0	Limit: 9	State:0x08 Tracked
Copy the code

In the code above, Priority:10 is the process I tested. At this time, the App is in the foreground and active, so the Priority is 10, and the OOM memory threshold is 650

Verification Scheme 2:

When our App is killed due to Jetsam, there will be a system log in the mobile phone. You can get the log at the beginning of JetsamEvent from the operation path of mobile phone setting – privacy – analysis. For example, on my 6s, the pageSize * rpages value is the threshold, and the reason is “Reason” : “Per-process-limit” (Not all JetSamEvents have the exact threshold, some are biased…)

"pageSize" : 16384
{
    "uuid" : "b8d6682c-5903-3007-b9c2-561d1e6ca9d5"."states" : [
      "frontmost"."resume"]."killDelta" : 18859,
    "genCount": 0."age" : 1775369503,
    "purgeable": 0."fds": 50."coalition" : 691,
    "rpages" : 89600,
    "reason" : "per-process-limit"."pid" : 960,
    "cpuTime": 1.6920809999999999."name" : "MemoryLimitTest"."lifetimeMax": 34182}Copy the code

Verification Scheme 3:

There are plenty of tests to find out what its OOM memory threshold is, and StackOverFlow already has a list of oom thresholds for some common devices. There is a deviation between the list threshold and the real threshold. I guess there are two reasons. First, the timing of its memory retrieval cannot be exactly the same as that of OOM, and it can only be as close as possible to this timing. The correct way to obtain memory is described below.

Results of testing with the utility Split wrote (link is inhis answer): device: (crash amount/total amount/percentage of total) iPad1: 127MB/256MB/49% iPad2: IPad 3: 645MB/1024MB/62% iPad4: 585MB/1024MB/57% (iOS 8.1) IPad Mini retina: 696MB/1024MB/68% (iOS 7.1) iPad Air: 697MB/1024MB/68% iPad Air 2: 1383MB/2048MB/68% (iOS 10.2.1) iPad Pro 9.7": 1395MB/1971MB/71% (iOS 10.0.2 (14A456)) iPad Pro 10.5 ": 3057/4000/76% (iOS 11 beta4) iPad Pro 12.9 "(2015): 3058/3999/76% (iOS 11.2.1) iPad Pro 12.9 "(2017): 3057/3974/77% (iOS 11 beta4) iPod Touch 4th Gen: IPod Touch 5th Gen: 286MB/512MB/56% (iOS 7.0) iPhone4: 325MB/512MB/63% iPhone4s: 286MB/512MB/56% iPhone5: 645MB/1024MB/62% iPhone5s: 646MB/1024MB/63% iPhone6: 645MB/1024MB/62% (iOS 8.x) iPhone6+: IPhone6s +: 1392MB/2048MB/68% (iOS 10.2.1) iPhone ese: IPhone7:1395MB/2048MB/ 68% (iOS 10.2) iPhone7+: 2040MB/3072MB/66% (iOS 10.2.1) iPhone X: 50% (1392/2785 / iOS 11.2.1) https://stackoverflow.com/questions/5887248/ios-app-maximum-memory-budget/15200855#15200855Copy the code

How to measure App memory usage correctly

A common way to get the App memory is to use resident_size as follows:

#import <mach/mach.h>

- (int64_t)memoryUsage {
    int64_t memoryUsageInByte = 0;
    struct task_basic_info taskBasicInfo;
    mach_msg_type_number_t size = sizeof(taskBasicInfo);
    kern_return_t kernelReturn = task_info(mach_task_self(), TASK_BASIC_INFO, (task_info_t) &taskBasicInfo, &size);

    if(kernelReturn == KERN_SUCCESS) {
        memoryUsageInByte = (int64_t) taskBasicInfo.resident_size;
        NSLog(@"Memory in use (in bytes): %lld", memoryUsageInByte);
    } else {
        NSLog(@"Error with task_info(): %s", mach_error_string(kernelReturn));
    }

    return memoryUsageInByte;
}
Copy the code

The correct way is to use phys_footprint, because Apple uses this metric, and keeping it consistent with Apple only makes sense. We can see the source code verify: opensource.apple.com/source/xnu/…

#import <mach/mach.h>

- (int64_t)memoryUsage {
    int64_t memoryUsageInByte = 0;
    task_vm_info_data_t vmInfo;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t kernelReturn = task_info(mach_task_self(), TASK_VM_INFO, (task_info_t) &vmInfo, &count);
    if(kernelReturn == KERN_SUCCESS) {
        memoryUsageInByte = (int64_t) vmInfo.phys_footprint;
        NSLog(@"Memory in use (in bytes): %lld", memoryUsageInByte);
    } else {
        NSLog(@"Error with task_info(): %s", mach_error_string(kernelReturn));
    }

    return memoryUsageInByte;
}
Copy the code

Oom location solution

Plan 1:

See the earliest oom related scheme is a blog post, FaceBook code.facebook.com/posts/11469… The OOM rate is calculated by elimination. Of course, there will be some errors between the result of this scheme and the actual data, for example, the ApplicationState is not accurate, and the watchdog is counted in oom.

Scheme 2:

Recently, Tencent has also opened its own OOM positioning solution, OOMDetector component: github.com/Tencent/OOM… . This approach uses the malloc_logger pointer in libmalloc to help developers locate large memory via the stack. However, there are also some defects, that is, frequent dump stack affects App performance, and only a small number of users can conduct data statistics and location.

Solution 3:

Based on recent discoveries, the App’s High Water Mark, or oom memory threshold, can be obtained offline. And that leads to plan 3

  • Monitor memory growth. When it reaches high water mark, dump memory information to obtain object name, object number and memory value of each object. If stability can be fully turned on, there will be no performance problems
  • The OOMDetector can get the stack of allocated memory, which is more efficient for locating code level; Can be grayscale open