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 state | Memory limit |
|---|---|
PERSISTENT | Unrestricted |
PERSISTENT_UI | Unrestricted |
TOP | Visible |
BOUND_TOP | Visible |
FOREGROUND_SERVICE | Not visible |
BOUND_FOREGROUND_SERVICE | Not visible |
IMPORTANT_FOREGROUND | Visible |
IMPORTANT_BACKGROUND | Not visible |
TRANSIENT_BACKGROUND | Not visible |
BACKUP | Not visible |
SERVICE | Not visible |
RECEIVER | Not visible |
TOP_SLEEPING | Visible |
HEAVY_WEIGHT | Not visible |
HOME | Not visible |
LAST_ACTIVITY | Not visible |
CACHED_ACTIVITY | Cached |
CACHED_ACTIVITY_CLIENT | Cached |
CACHED_RECENT | Cached |
CACHED_EMPTY | Cached |
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. minimumRequiredMemTotalThe 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
MemTotalvalue 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
MemTotalis lower than the device's advertised physical DRAM,minimumRequiredMemTotalthresholds include a margin for these carve-outs. For example, a configuration targeting 16 GB devices specifies aminimumRequiredMemTotalof15360(15 GiB) rather than16384(16 GiB), and a tier for 12 GB devices specifies11264(11 GiB). This ensures that each device matches its intended tier regardless of board-level carve-out variations.memVisibleThe soft memory limit (
memory.high) in MiB applied to visible processes.memNotVisibleThe soft memory limit (
memory.high) in MiB applied to not visible processes.swapVisibleThe swap space limit (
memory.swap.max) in MiB applied to visible processes.swapNotVisibleThe 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.highlimit, 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 statusExample 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.
visibleMemandnotVisibleMem- The calculated absolute memory limits (
memory.high) currently enforced for each visibility state. visibleSwapandnotVisibleSwap- 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.