Memory Limiter

Android 17 and higher includes the Memory Limiter, a system service that monitors and limits the memory usage of application processes using Linux cgroup v2. The Memory Limiter prevents individual apps from consuming disproportionate system memory, which reduces overall memory pressure and prevents aggressive low-memory kills of critical foreground and cached processes.

Mechanism

The Memory Limiter integrates with the Activity Manager Service (AMS) to track process lifecycle events and state changes. The Memory Limiter enforces memory limits using the Linux kernel cgroup v2 hierarchy.

To support the Memory Limiter, the device kernel must enable cgroup v2 and the memory controller. The service specifically relies on the following attributes:

memory.high
A soft limit. When exceeded, the process is throttled and the kernel attempts proactive page reclaim from that process.
memory.swap.max
Limits the maximum amount of swap space (such as ZRAM) the process can use.

Impact on apps

Apps that operate within their memory limits aren't affected by the Memory Limiter.

When an app crosses its memory.high limit, the kernel evicts the app's clean file-backed memory and swaps out its inactive anonymous memory to keep the app within the limit. As a result of this page eviction and swap activity, the app might experience temporary execution throttling, but it continues to run.

In extreme situations—such as an uncontrolled memory leak where the app continues allocating anonymous memory after swap space is exhausted—the app fails to allocate memory and is terminated.

Process monitoring

The Memory Limiter monitors app processes (UID >= 10000) by default. Core system processes are exempt to preserve essential system stability.

The Memory Limiter assigns memory limits based on the visibility state of the process:

  • Visible processes: Processes hosting a user-visible UI (such as the currently focused activity or an active interaction). Because displaying a UI requires a larger working set for rendering and graphics pipelines, visible processes receive a more generous memory limit.

  • Not visible processes: Processes performing work in the background without presenting a UI (such as background services or broadcast receivers). Because they don't render UI components, they operate under a more restrictive limit.

The following table maps specific process states to memory limit classifications:

Process stateMemory limit
PERSISTENTUnrestricted
PERSISTENT_UIUnrestricted
TOPVisible
BOUND_TOPVisible
FOREGROUND_SERVICENot visible
BOUND_FOREGROUND_SERVICENot visible
IMPORTANT_FOREGROUNDVisible
IMPORTANT_BACKGROUNDNot visible
TRANSIENT_BACKGROUNDNot visible
BACKUPNot visible
SERVICENot visible
RECEIVERNot visible
TOP_SLEEPINGVisible
HEAVY_WEIGHTNot visible
HOMENot visible
LAST_ACTIVITYNot visible
CACHED_ACTIVITYCached
CACHED_ACTIVITY_CLIENTCached
CACHED_RECENTCached
CACHED_EMPTYCached

In the cached state, processes are frozen and then maximally reclaimed.

When a process exceeds its assigned memory.high limit, the Memory Limiter detects the event and can trigger diagnostic actions, such as capturing a memory profile or logging an anomaly to statsd.

Configuration

The Memory Limiter reads its configuration from the vendor partition:

  • File path: /vendor/etc/memory-limiter-config.xml

XML format

The configuration file follows the schema defined in memory-limiter-config.xsd. The file defines multiple limit sets ordered by available memory; the service selects the highest matching limit set based on the device's available RAM. All memory values are specified in mebibytes (MiB).

<MemoryLimiterConfig>
  <version>1</version>
  <configList>
    <!-- RAM minimums account for MemTotal excluding carve-outs. -->
    <limitSet>
      <!-- Limits for 16GB RAM device (MemTotal >= 15GiB): 10G/5G/5G/5G -->
      <minimumRequiredMemTotal>15360</minimumRequiredMemTotal>
      <memVisible>10240</memVisible>
      <memNotVisible>5120</memNotVisible>
      <swapVisible>5120</swapVisible>
      <swapNotVisible>5120</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 12GB RAM device (MemTotal >= 11GiB): 8G/4G/4G/4G -->
      <minimumRequiredMemTotal>11264</minimumRequiredMemTotal>
      <memVisible>8192</memVisible>
      <memNotVisible>4096</memNotVisible>
      <swapVisible>4096</swapVisible>
      <swapNotVisible>4096</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 8GB RAM device (MemTotal >= 7GiB): 5G/3G/3G/3G -->
      <minimumRequiredMemTotal>7168</minimumRequiredMemTotal>
      <memVisible>5120</memVisible>
      <memNotVisible>3072</memNotVisible>
      <swapVisible>3072</swapVisible>
      <swapNotVisible>3072</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 6GB RAM device (MemTotal >= 5GiB): 4G/2G/2G/2G -->
      <minimumRequiredMemTotal>5120</minimumRequiredMemTotal>
      <memVisible>4096</memVisible>
      <memNotVisible>2048</memNotVisible>
      <swapVisible>2048</swapVisible>
      <swapNotVisible>2048</swapNotVisible>
    </limitSet>
    <limitSet>
      <!-- Limits for 4GB RAM device (MemTotal >= 3GiB): 2G/1G/1G/1G -->
      <minimumRequiredMemTotal>3072</minimumRequiredMemTotal>
      <memVisible>2048</memVisible>
      <memNotVisible>1024</memNotVisible>
      <swapVisible>1024</swapVisible>
      <swapNotVisible>1024</swapNotVisible>
    </limitSet>
  </configList>
</MemoryLimiterConfig>
version
A positive integer identifying the configuration version. This must be 1.
minimumRequiredMemTotal

The minimum system MemTotal (in MiB) required for this limit set to apply. The service evaluates this against the total available memory reported by the kernel in /proc/meminfo.

Understanding memory carve-outs: The kernel MemTotal value reflects the physical DRAM accessible to the operating system after hardware and firmware memory carve-outs are deducted. Carve-outs reserve RAM for specialized hardware components such as the GPU, baseband modem, camera ISP, secure execution environments, and hypervisors, typically consuming between 500 MiB and over 1 GiB of physical memory.

Because MemTotal is lower than the device's advertised physical DRAM, minimumRequiredMemTotal thresholds include a margin for these carve-outs. For example, a configuration targeting 16 GB devices specifies a minimumRequiredMemTotal of 15360 (15 GiB) rather than 16384 (16 GiB), and a tier for 12 GB devices specifies 11264 (11 GiB). This ensures that each device matches its intended tier regardless of board-level carve-out variations.

memVisible

The soft memory limit (memory.high) in MiB applied to visible processes.

memNotVisible

The soft memory limit (memory.high) in MiB applied to not visible processes.

swapVisible

The swap space limit (memory.swap.max) in MiB applied to visible processes.

swapNotVisible

The swap space limit (memory.swap.max) in MiB applied to not visible processes.

Standard platform limits

In Android 17 and higher, the platform specifies standard memory and swap limits in /vendor/etc/memory-limiter-config.xml across common physical RAM tiers:

Physical RAM Minimum required
MemTotal
(minimumRequiredMemTotal)
Visible memory limit
(memVisible)
Not visible memory limit
(memNotVisible)
Visible swap limit
(swapVisible)
Not visible swap limit
(swapNotVisible)
16 GB 15,360 MiB (15 GiB) 10,240 MiB (10 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB) 5,120 MiB (5 GiB)
12 GB 11,264 MiB (11 GiB) 8,192 MiB (8 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB) 4,096 MiB (4 GiB)
8 GB 7,168 MiB (7 GiB) 5,120 MiB (5 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB) 3,072 MiB (3 GiB)
6 GB 5,120 MiB (5 GiB) 4,096 MiB (4 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB) 2,048 MiB (2 GiB)
4 GB 3,072 MiB (3 GiB) 2,048 MiB (2 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB) 1,024 MiB (1 GiB)

Memory limit principles

The Memory Limiter configuration is designed around the following platform principles:

  • Ecosystem consistency and app compatibility: Consistent memory limits across devices ensure predictable app performance throughout the Android ecosystem. Apps are developed and tested against standard memory expectations; maintaining uniform platform limits avoids unexpected throttling or premature terminations.

  • Proportional resource allocation: The platform limits are calibrated according to physical RAM capacity:

    • Visible processes: Allocated approximately 1/2 to 2/3 of total physical RAM to support active UI, composition, and rendering workloads.
    • Not visible processes: Allocated approximately 1/4 to 1/3 of total physical RAM for background tasks.
  • Universal applicability: Limits apply uniformly to all application processes on the device (UID >= 10000), including preinstalled system and OEM apps. The Memory Limiter doesn't support allowlists to exempt specific apps, ensuring fair memory governance across all apps.

  • Absence of runtime query APIs: In Android 17 and higher, apps can't programmatically query their assigned memory limits at run time. The standard platform limits are defined with generous margins so that well-behaved apps operate without constraints during normal usage.

  • Kernel reclaim and swap dynamics: When an app process approaches its memory.high limit, the Linux kernel directs memory reclaim specifically at that process cgroup. This includes dropping inactive file-backed pages and swapping cold anonymous pages to ZRAM, maintaining system fluidity without imposing global memory pressure.

Shell commands

The am memory-limiter command lets developers and system integrators interact with the service at run time for development, testing, and debugging:

am memory-limiter <SUB-COMMAND>

status

The status sub-command reports the operational status and active metrics of the Memory Limiter:

adb shell am memory-limiter status

Example output:

Memory limiter
  enabled                  monitoring=true          ignored=none
  visibleMem=1948MB        visibleSwap=974MB
  notVisibleMem=974MB      notVisibleSwap=487MB
  started=36               watched=36               watch-failed=0
  events=0                 processes=36             process-hwm=36

Key fields in the output include:

monitoring
Indicates whether the Memory Limiter is actively watching processes.
visibleMem and notVisibleMem
The calculated absolute memory limits (memory.high) currently enforced for each visibility state.
visibleSwap and notVisibleSwap
The calculated absolute swap limits (memory.swap.max) currently enforced for each visibility state.
events
The number of times a process has crossed its assigned limit.
processes
The current number of monitored processes.

ignore

The ignore sub-command temporarily excludes a specific UID or all processes from memory limiting. This is useful for performance benchmarking, stress testing, or diagnosing memory behavior:

# Ignore a specific UID
adb shell am memory-limiter ignore 10087
# Ignore all processes (temporarily disables limiting)
adb shell am memory-limiter ignore all
# Resume normal limiting operation
adb shell am memory-limiter ignore none

manual

The manual sub-command overrides the calculated limits for a specific process by process ID (PID) with a custom absolute value in bytes. The value must be an integer, but can include the MB suffix to indicate MiB or the GB suffix to indicate GiB:

# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1073741824
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1024MB
# Set a 1GiB limit for PID 1234
adb shell am memory-limiter manual 1234 1GB
# Remove the manual override for PID 1234
adb shell am memory-limiter manual 1234 none

Manual overrides apply only for the lifetime of that specific process instance. If the process restarts, it returns to the standard limits based on its state.

A manual override can't exceed physical system limits.