On this page · 11 sections
- What the two Google documents actually say
- The per-tier caps AOSP publishes
- The foreground service trap
- How to tell if this already happened to your users
- How to test before your users do
- What actually changes in the build
- India-specific considerations
- What is still unknown
- FAQ
- How eCorpIT can help
- References
Summary. Android 17 (API level 37) enforces per-app memory caps that scale with device RAM. The Android Open Source Project publishes the actual numbers, and the Android Developers Blog does not: on a 6 GB device the cap is 2048 MiB for a visible process and 1024 MiB for a background one, with a matching 2048 MiB and 1024 MiB swap ceiling. On a 16 GB device it is 10240 MiB visible and 5120 MiB background. The two Google documents also disagree on what happens when you cross the line. The blog post of 2 June 2026 says Android "will kill the process with no associated stack trace." The AOSP Memory Limiter page says memory.high is a soft limit, the kernel throttles the process and reclaims pages, and the app "continues to run" until swap is exhausted. The gap matters, because the mitigation for a hard kill is different from the mitigation for a throttle. And one detail buried in the AOSP process-state table will surprise most Android teams: FOREGROUND_SERVICE is classified as "Not visible", so a media player or a sync service running behind a notification gets the smaller cap, not the larger one. IDC put India's smartphone average selling price at a record US$302 in Q1 2026, up 10.4% year over year, with the sub-US$100 band down 59% and its share falling from 18% to 8% as memory costs climbed. India's install base skews to exactly the 4 GB and 6 GB tiers where these caps bite hardest.
What the two Google documents actually say
The Android Developers Blog published "Prioritizing Memory Efficiency: Essential Steps for Android 17" on 2 June 2026, written by Alice Yuan, Ajesh Pai and Fung Lam of Developer Relations. Its framing is blunt: "To ensure device stability, starting in Android 17, the system will begin enforcing app memory limits based on the device's total RAM. If an app exceeds those limits, Android will kill the process with no associated stack trace."
The AOSP Memory Limiter page describes the same feature at the kernel level and reads differently. The Memory Limiter is a system service that uses Linux cgroup v2. It relies on two attributes: memory.high, described as "A soft limit. When exceeded, the process is throttled and the kernel attempts proactive page reclaim from that process", and memory.swap.max, which bounds ZRAM usage. The AOSP text then says that when an app crosses memory.high, the kernel evicts clean file-backed pages and swaps out inactive anonymous memory, and "the app might experience temporary execution throttling, but it continues to run." Termination is reserved for the extreme case: "an uncontrolled memory leak where the app continues allocating anonymous memory after swap space is exhausted."
Both statements can be true. The blog describes the endpoint your crash dashboard sees; AOSP describes the mechanism that precedes it. But if you plan only for the kill, you will miss the more common outcome, which is an app that stays alive and gets slower. Jank that appears only on 4 GB and 6 GB devices, only after the user has been in the app for a while, is the signature.
The per-tier caps AOSP publishes
The platform default lives in /system/etc/memory-limiter-config.xml, with an optional vendor override at /vendor/etc/memory-limiter-config.xml. The service reads the override first and falls back to the system file. Limit sets are selected by minimumRequiredMemTotal, evaluated against MemTotal in /proc/meminfo, and every value is in mebibytes.
| Target physical RAM | minimumRequiredMemTotal | Visible memory cap | Background memory cap | Swap cap (visible / background) |
|---|---|---|---|---|
| 16 GB | 13312 MiB | 10240 MiB | 5120 MiB | 5120 / 5120 MiB |
| 12 GB | 10240 MiB | 8192 MiB | 4096 MiB | 4096 / 4096 MiB |
| 8 GB | 6656 MiB | 5120 MiB | 3072 MiB | 3072 / 3072 MiB |
| 6 GB | 4608 MiB | 4096 MiB | 2048 MiB | 2048 / 2048 MiB |
| 4 GB | 3072 MiB | 2048 MiB | 1024 MiB | 1024 / 1024 MiB |
The thresholds sit well below the advertised RAM on purpose. AOSP explains that MemTotal reflects DRAM left after hardware carve-outs for the GPU, baseband modem, camera ISP, secure execution environments and hypervisors, "typically consuming between 500 MiB and over 1 GiB of physical memory." So the 16 GB tier asks for 13312 MiB rather than 16384 MiB, and the 12 GB tier asks for 10240 MiB. A device that ships with 8 GB but carves out 1.5 GB can fall through to the 6 GB limit set. Do not assume the marketing spec picks the tier.
Because a vendor file overrides the system file, the caps on a specific handset are a device-level fact, not a platform-level one. Test on the OEM hardware your users actually carry.
The foreground service trap
AOSP maps every process state to a limit class. The mapping is not the one most Android teams would guess.
| Process state | Limit class applied |
|---|---|
TOP, BOUND_TOP, TOP_SLEEPING |
Visible |
IMPORTANT_FOREGROUND |
Visible |
FOREGROUND_SERVICE, BOUND_FOREGROUND_SERVICE |
Not visible |
SERVICE, RECEIVER, BACKUP, HEAVY_WEIGHT |
Not visible |
HOME, LAST_ACTIVITY, TRANSIENT_BACKGROUND |
Not visible |
CACHED_ACTIVITY, CACHED_RECENT, CACHED_EMPTY |
Cached (frozen, then maximally reclaimed) |
PERSISTENT, PERSISTENT_UI |
Unrestricted |
FOREGROUND_SERVICE sits in the restrictive class. On a 6 GB phone that is 2048 MiB, not 4096 MiB. The reasoning is in the blog post: the whole point of the feature is that an app "holding a privileged state (e.g. it's running a Foreground Service)" was previously shielded from the Low Memory Killer while it hoarded RAM, forcing the LMK to kill dozens of well-behaved cached apps instead. Android 17 closes that shield.
The apps most exposed are the ones that do heavy work behind a notification: audio and video playback with large decode buffers, offline map rendering, camera and media export pipelines, background data synchronization, and anything doing on-device inference outside a visible activity. If you have moved model execution into a foreground service, read the tier numbers again with your peak resident set in hand. Our own reading of the on-device versus cloud AI decision for mobile apps already pushed teams to size the memory budget first; Android 17 turns that budget into an enforced ceiling on the small tiers.
Note also that PERSISTENT and PERSISTENT_UI are unrestricted, and AOSP says the Memory Limiter monitors processes with UID >= 10000 by default, exempting core system processes. Your app is not in that exempt set.
How to tell if this already happened to your users
There is no crash and no stack trace, so the signal is in exit reasons rather than in your crash reporter's default view. Both Google documents give the same detection recipe. Call getDescription() on `ApplicationExitInfo`. If the Memory Limiter applied a limit, the exit reason is REASON_OTHER and the description string contains MemoryLimiter:AnonSwap.
That exact string is the thing to grep for. An exit bucketed as REASON_OTHER with no further parsing is invisible in most dashboards, which is how a memory-limit termination gets filed as "user closed the app."
For heap evidence, Android 17 adds two ProfilingManager triggers on top of the API introduced in Android 15. TRIGGER_TYPE_OOM captures a Java heap dump at the moment an OutOfMemoryError is thrown and hands you the profile on the next app start, once you register registerForAllProfilingResults. TRIGGER_TYPE_ANOMALY fires on breached memory thresholds and delivers a heap dump immediately before the system terminates the app. Analyse the result in the Perfetto Heap Dump Explorer, which is now the default heap-dump view. Separately, AOSP notes that a memory.high breach can log an anomaly to statsd, and Google says Play Console will surface more in-field memory metrics, without naming a date.
How to test before your users do
The Android 17 behavior-changes documentation adds three am subcommands, reachable over adb, that let you impose or lift the constraint on a real device. They do nothing on a device that imposes no limits.
am memory-limiter ignore <uid>|none|all
am memory-limiter manual <pid> <limit>|max|none
am memory-limiter status
ignore takes a UID and exempts every process under it, or all to exempt everything, or none to clear previous exemptions. manual takes a PID and an integer number of megabytes, so passing 30 pins that process to 30 MB; max removes all limits on it and none restores the system default. status reports the limits currently applied to visible and non-visible processes.
The practical test loop is short. Read status on the target device to learn its real tier. Use manual to pin your process at the background cap for that tier, drive the app through its heaviest path with the UI hidden, and watch for the MemoryLimiter:AnonSwap exit. That reproduces on a 12 GB developer handset what a user on a 4 GB device will hit in production.
What actually changes in the build
The remediation list Google publishes has not changed since June, and none of it is new engineering. Enable full R8: set isShrinkResources = true and isMinifyEnabled = true, switch from proguard-android.txt to proguard-android-optimize.txt (the legacy file blocks optimisation and is no longer supported in Android Gradle Plugin 9), and remove android.enableR8.fullMode = false from gradle.properties. Narrow the keep rules; global -dontoptimize, -dontshrink and -dontobfuscate switches turn the whole thing off. Google's published example is Monzo, which enabled full R8 optimisation and recorded a 35% reduction in ANR rate, a 30% improvement in cold start rate and a 9% reduction in app size.
Bitmaps are the next lever, because a 100 KB compressed file decodes into several megabytes of resident pixels. Downsample rather than loading full-resolution images into small views, use RGB_565 instead of the default ARGB_8888 wherever transparency is not needed (half the memory), prefer vector drawables for geometric assets, and return bitmaps to the pool in Coil or Glide instead of allocating fresh ones.
Then implement onTrimMemory in your Application class through `ComponentCallbacks2`, and handle only TRIM_MEMORY_UI_HIDDEN and TRIM_MEMORY_BACKGROUND. Since Android 14 the system has stopped delivering the other legacy constants, and they were formally deprecated in Android 15. Code that still branches on TRIM_MEMORY_MODERATE or TRIM_MEMORY_RUNNING_LOW is dead code. That matters more under Android 17 than it did before, because TRIM_MEMORY_UI_HIDDEN is now the moment your process moves into the smaller cap.
Teams already working through the wider Android 17 API 37 behavior changes should treat memory as a separate workstream from the targetSdk bump. Memory limits apply to all apps on Android 17 regardless of the level you target, so they land before you touch targetSdk 37 and the adaptive layout work.
India-specific considerations
The tier table is a pricing question in India, not just an engineering one. IDC reported India's smartphone shipments down 4.1% year over year to 31.0 million units in Q1 2026, with average selling price at a record US$302, up 10.4%. The sub-US$100 band fell 59% and its share halved from 18% to 8%, while the US$100 to US$200 band grew 10% and rose from 39% to 45% share.
Aditya Rampal, senior research analyst, Devices Research, IDC Asia Pacific, attributed the move to component costs: "Average selling prices increased 10.4% YoY to a record US$302 in Q1 2026, driven by persistent memory cost inflation across both newly launched devices and existing models."
Memory inflation pushes new budget models toward less RAM, not more. For an Indian consumer app that means a large and growing share of sessions on the 4 GB and 6 GB limit sets, where the background cap is 1024 MiB or 2048 MiB. A design that assumes an 8 GB floor is designing for the shrinking half of the market. Under the DPDP Act 2023, any local caching you add to reduce memory churn still has to respect purpose limitation and retention, so cache aggressively for performance and clear it on the same schedule you already publish.
What is still unknown
Three things are not yet documented. Google has not published a date for the in-field memory metrics it says are coming to Play Console. There is no published policy tying memory-limit terminations to Play technical-quality thresholds the way ANR and crash rates are tied today, so the enforcement is a platform behaviour and not yet a store one. And because the vendor override exists, there is no way to enumerate the real caps across the Android fleet from the AOSP defaults alone; a device-level survey is the only honest answer. The real cost here is usually the measurement, not the fix.
FAQ
How eCorpIT can help
eCorpIT is a CMMI Level 5 and ISO 27001:2022 certified engineering organisation in Gurugram, and our Android teams run memory work as a measured exercise rather than a guess: read the real tier off the device, pin the process with am memory-limiter manual, and reproduce the failure before changing code. That is the same instrumentation loop behind our Android vitals ANR and crash remediation work, and it fits inside the broader enterprise mobile app development programmes we run. If your app leans on a foreground service and ships to 4 GB and 6 GB devices, talk to our senior engineering team at /contact-us/.
References
- Memory Limiter, Android Open Source Project - mechanism, process-state table, XML schema and the standard platform limit sets.
- Prioritizing Memory Efficiency: Essential Steps for Android 17, Android Developers Blog, 2 June 2026 - Google's enforcement framing, detection recipe and remediation list.
- Manage your app's memory, Android Developers - the memory guidance Google points to for baselines and diagnosis.
- ApplicationExitInfo, Android API reference -
getDescription()and theREASON_OTHERexit reason.
- ComponentCallbacks2, Android API reference -
onTrimMemory()and the surviving trim levels.
- Android Debug Bridge (adb), Android Developers - the shell used to run the
am memory-limitersubcommands.
- Low memory killer daemon, Android Open Source Project - the LMK behaviour the Memory Limiter is designed to relieve.
- Cgroup abstraction layer, Android Open Source Project - the cgroup v2 layer the Memory Limiter builds on.
- Keep rules best practices, Android Developers - narrowing R8 keep rules to recover optimisation.
- Coil image loading library - Google's recommended loader for Kotlin and Jetpack Compose projects.
- Glide image loading library - Google's recommended loader for Java-based applications.
- Heap Dump Explorer, Perfetto documentation - the default heap-dump view for profiles captured by ProfilingManager triggers.
- Why India's Smartphone Market Declined 4.1% in Q1 2026, IDC, 12 May 2026 - shipment, ASP and price-band figures, and the Aditya Rampal quote.
Last updated 20 August 2026.