Mobile Development 19 min read

Uncovering iOS OOM: From Kernel Mechanics to Real‑World Monitoring Solutions

This article delves into the hidden iOS OOM crashes, explains the Jetsam watchdog mechanism, the role of phys_footprint, compares industry solutions, and provides step‑by‑step implementations for monitoring, allocation tracking, and proactive memory‑management strategies to prevent silent terminations.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Uncovering iOS OOM: From Kernel Mechanics to Real‑World Monitoring Solutions

Background

iOS apps may terminate without a crash log because the kernel’s Jetsam daemon kills the process with SIGKILL when memory pressure exceeds a device‑specific limit. This out‑of‑memory (OOM) termination is invisible to crash‑monitoring services.

iOS Memory Model

Each process has a 16 EB virtual address space but can only use the physical RAM on the device. Memory is managed in 16 KB pages and classified as:

Clean Memory – pages that can be paged out (framework code, read‑only constants, mmap ‑ed files).

Dirty Memory – non‑page‑outable pages such as the malloc heap, image decode buffers, and object instance variables. This is the main source of OOM.

Compressed Memory – inactive dirty pages that the system compresses instead of swapping to flash.

The effective memory usage is AppMemoryUsage = DirtyMemory + CompressedMemory .

Jetsam Watchdog

The daemon com.apple.jetsam (kernel thread Jetsam) monitors physical memory pressure. When free memory falls below a threshold it kills processes according to a priority list, starting with background‑suspended apps. The decision is based on the phys_footprint field from task_vm_info (defined in osfmk/mach/task_info.h), which reflects the real pressure on RAM. Approximate thresholds are:

iPhone 6s (2 GB RAM): 1.2 GB – 1.3 GB

iPhone X (3 GB RAM): 1.6 GB – 1.8 GB

iPhone 14 Pro (6 GB RAM): > 3 GB

Detecting Silent OOM

Because Jetsam kills the process with SIGKILL, no crash log is written. A simple exclusion method records a persistent flag at launch ( AppStatus = Started) and updates it on normal crash, user‑initiated quit, or OS update. On the next launch, if the flag is still Started and no crash log exists, the termination is inferred as OOM. iOS 10+ distinguishes foreground OOM (FOOM) and background OOM (BOOM) via UIApplicationDidEnterBackgroundNotification.

Monitoring Options

MetricKit (system‑level) – provides authoritative OOM counts, exit metrics and diagnostic payloads once per day with zero performance impact.

Custom allocation tracking – hooks memory allocation functions to record large allocations and their backtraces.

Accurate Memory Usage

#import <mach/mach.h>

+ (int64_t)getAppMemoryUsage {
    struct task_vm_info info;
    mach_msg_type_number_t count = TASK_VM_INFO_COUNT;
    kern_return_t kr = task_info(mach_task_self(),
                                 TASK_VM_INFO,
                                 (task_info_t)&info,
                                 &count);
    if (kr == KERN_SUCCESS) {
        return (int64_t)info.phys_footprint;
    }
    return 0;
}

malloc_logger Hook (private API)

typedef void (malloc_logger_t)(uint32_t type,
                                 uintptr_t arg1,
                                 uintptr_t arg2,
                                 uintptr_t arg3,
                                 uintptr_t result,
                                 uint32_t num_hot_frames_to_skip);
extern malloc_logger_t *malloc_logger;
static malloc_logger_t *orig_malloc_logger = NULL;

void my_malloc_logger(uint32_t type,
                      uintptr_t arg1,
                      uintptr_t arg2,
                      uintptr_t arg3,
                      uintptr_t result,
                      uint32_t skip) {
    if (type & MALLOC_OP_ALLOC) {
        size_t size = (size_t)arg1;
        if (size > 10 * 1024 * 1024) {   // record allocations >10 MB
            recordAllocationStack(size, result);
        }
    } else if (type & MALLOC_OP_FREE) {
        // remove record
    }
    if (orig_malloc_logger) {
        orig_malloc_logger(type, arg1, arg2, arg3, result, skip);
    }
}

void installMemoryHook(void) {
    orig_malloc_logger = malloc_logger;
    malloc_logger = my_malloc_logger;
}

Backtraces should be captured as raw PC addresses (e.g., using backtrace()) and stored in a ring buffer; symbolication is performed after an OOM event.

Architecture Overview

Collector Layer

Memory Sentinel – periodic phys_footprint polling (e.g., every 2 s).

Allocation Hook – malloc_logger based monitor.

MMap Recorder – writes stack data to an mmap ‑backed file so it survives a sudden Jetsam kill.

Policy Layer

Sampling control – cloud‑driven device percentage.

Threshold control – dynamic size thresholds.

Circuit‑breaker – disables the hook if it itself causes crashes.

Analyzer Layer

Symbolization service – server‑side dSYM lookup.

Clustering – groups similar OOM stacks into a top‑list.

Alerting – sends alerts (e.g., Feishu/DingTalk) when OOM rate exceeds a red line.

Practical Recommendations

Never use mach_task_basic_info.resident_size; prefer task_vm_info.phys_footprint.

Filter allocation records by a size threshold (e.g., > 50 KB) to reduce overhead.

Capture backtraces with backtrace() and store raw pointers; symbolicate later with dSYM.

Prevent recursion in malloc_logger by setting a thread‑local flag before entering the hook.

Downsample large images using ImageIO thumbnail APIs to keep DirtyMemory low.

Detect and fix memory fragmentation, leaks, and autorelease‑pool misuse.

MetricKit vs Custom Monitoring

MetricKit offers zero‑overhead, kernel‑authoritative OOM counts but delivers data with a ~24 h delay and without per‑event context. Custom monitoring provides real‑time alerts and detailed allocation stacks but may miss some events. The recommended practice is a hybrid approach: use MetricKit as the ground‑truth source and calibrate the custom solution’s thresholds to achieve timely alerts and high accuracy.

Future Directions

Predictive OOM avoidance can be built with lightweight machine‑learning models that analyse the memory‑growth slope of a running session and proactively release caches or view controllers before phys_footprint reaches the device limit.

Key Takeaways

Trust phys_footprint over resident_size for OOM detection.

Combine system‑level MetricKit data with sampled custom hooks for a balanced solution.

Adopt a layered architecture (collector → policy → analyzer) to keep monitoring stable and extensible.

PerformanceiOSMemoryHookOOMMetricKitJetsam
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.