Skip to main content
Blog
android

Android 17 app memory limits, explained: what they are, why Google added them, and how to test your app before the stable release

Android 17 introduces per-app memory caps based on device RAM. Here's how the limits work, how to detect when you hit one, and how to fix the patterns that trip them.

Carlton Aikins9 min read

Android 17 reached its fourth and final beta on April 17, 2026, with a new behavior change that has gotten less coverage than it deserves: every app on every Android 17 device will now run under a memory cap that scales with the device's total RAM. The cap is set conservatively in this first version, and Google says most app sessions won't notice. But "most" is doing a lot of work in that sentence — the apps that do notice are the ones most likely to be on indie devs' Play Console pages right now: image-heavy apps, apps that load a lot of media on launch, anything that took the "throw it in memory and forget" approach because mobile RAM kept getting cheaper.

This guide walks through what actually changed, why Google made the change, how the cap is enforced and detected, and what to do this week — before the stable release in June — to make sure your app doesn't start hitting MemoryLimiter:AnonSwap exits in the wild.

What changed in Android 17#

Until Android 17, the only meaningful per-app memory limit on Android was the heap cap, which has been around for over a decade and varies wildly by device. Beyond the heap, an app could consume as much native memory, mapped memory, and graphics memory as the system would tolerate before the low-memory killer started reaping background processes. The system was self-correcting, but the correction was always reactive — the OS would only push back after the device started suffering.

Android 17 changes the model from reactive to predictive. The OS now applies a per-app memory ceiling based on the device's total RAM, and an app that exceeds it is terminated with a specific exit reason. The cap is intentionally conservative in the first release — Google has said it's targeting outliers and obvious leaks rather than typical workloads — but it is a hard limit, not a hint.

The Beta 4 release notes describe the new limits as part of a broader push to "create a more stable and deterministic environment" by clamping down on the apps that were dragging system performance down for everyone. In practice, this means apps that slowly grow memory over a session, apps that never release bitmaps, and apps that hold onto large native buffers for convenience are now in the firing line.

Why Google did this now#

Three things changed at the same time, and the memory cap is the convergence of all three.

The first is the device RAM ceiling. For years, flagship phones got more RAM and the limits got softer. That trend has flattened. The cheapest billion-target Android devices Google now sells in emerging markets have far less RAM than the assumptions baked into a lot of apps' memory budgets, and the gap between "what an app uses on a Pixel 9 Pro" and "what fits on a 4 GB budget device" has gotten unmanageable.

The second is the rise of background AI workloads. On-device models — Gemini Nano, AI Core, the new Gemma 4 deployments — consume meaningful chunks of RAM, and the OS needs them to be available when the user invokes them. If a foreground app has eaten 80% of available RAM, the AI subsystem can't operate, and the experience degrades for everyone.

The third is system-wide stability data. Google has been publishing increasingly explicit data on the link between memory pressure and the metrics they actually care about — UI jank, ANRs, force closes, and battery drain — and the conclusion they keep reaching is that a small number of misbehaving apps cause a disproportionate share of system-wide pain. The cap is a way to surface those apps to their developers via crashes instead of letting them quietly poison the device experience.

How the limit actually works#

The cap is calculated per device based on total RAM, not on an absolute number. Google has not published the exact formula, but the implementation pattern is clear from the Beta 4 release notes and the new APIs:

The system tracks a measure of "allocated anonymous memory" per app — essentially the heap plus native buffers that aren't backed by a file. When that measure crosses the device-specific threshold, the OS triggers a memory-limit kill. The app is terminated, not paused, and the user sees the same recent-apps behavior as any other process death.

When this happens, you can detect it after the fact through the existing ApplicationExitInfo API, which Android has exposed since API 30. The new wrinkle: a memory-limiter kill shows up with getReason() == REASON_OTHER and a description that contains the literal string MemoryLimiter:AnonSwap. That string is the only reliable signal that this specific cap killed your app — generic OOM or low-memory kills look different.

Here's the detection pattern:

kotlin
val activityManager = getSystemService(ACTIVITY_SERVICE) as ActivityManager
val recentExits = activityManager.getHistoricalProcessExitReasons(
    packageName, /* pid */ 0, /* maxNum */ 10
)

recentExits
    .filter { it.reason == ApplicationExitInfo.REASON_OTHER }
    .filter { it.description?.contains("MemoryLimiter:AnonSwap") == true }
    .forEach { exit ->
        // log, send to crashlytics, alert ops
        Log.w("memcheck", "memory limit hit at ${exit.timestamp}, pss=${exit.pss}")
    }

This should run early on app launch, ideally in your Application.onCreate, so you find out about yesterday's silent deaths today. If you ship to a meaningful fraction of Android 17 devices and don't have this hooked up, you will not know you're being killed.

For the deeper investigation step, Android 17 also extends trigger-based profiling with a new TRIGGER_TYPE_ANOMALY value. You can configure your debug builds to capture a heap dump at the moment the limit is breached, which is dramatically more useful than the post-hoc forensics you used to be stuck with.

The patterns most likely to trip the cap#

A short tour of the antipatterns I see hitting this in the wild during the Beta period.

Bitmap caches without eviction. A LruCache<String, Bitmap> or a Glide configuration sized for "lots of headroom" looks fine on a Pixel 8 Pro and explodes on a 4 GB device. If your image cache budget is a fixed percentage of Runtime.getRuntime().maxMemory(), you are sized correctly. If it's a hard-coded number of MB, audit it.

Unreleased native buffers. Anything with a JNI layer — image processing libraries, ML runtimes, custom video pipelines — has a habit of leaking native allocations that the JVM heap stats can't see and the GC can't reclaim. The new memory limit counts these. If you're using TensorFlow Lite, OpenCV, or a custom NDK module, profile native allocation specifically.

Background services holding onto pages. A long-running foreground service that maintains a 200 MB working set is a much bigger problem in Android 17 than in Android 16. The OS is now treating that as a deliberate over-allocation rather than a temporary spike, and it will be the first thing killed under pressure.

Bitmap-heavy onboarding flows. Apps that load a dozen full-resolution onboarding images at once and then never free them. The screen flow ends, the user moves on, and 80 MB sits in the heap forever. This was always wasteful; it's now actively dangerous.

Forgotten WebViews. Holding a WebView reference past the screen that needed it. WebView memory consumption has grown across Android versions, and a leaked WebView is one of the fastest ways to trip the cap on cheaper devices.

A concrete pre-stable checklist#

The Android 17 stable release is expected in early-to-mid June, after Beta 4. That gives indie devs about six weeks. Here's what to do with them.

Week 1. Wire up ApplicationExitInfo polling at app launch. Pipe MemoryLimiter:AnonSwap exits to whatever crash reporter you use — Crashlytics, Sentry, your own logger. You want a baseline before the stable rolls out, so you can tell signal from noise after.

Week 2. Profile a typical 30-minute session on the lowest-RAM device you ship for. Use Android Studio's Memory Profiler with native allocation tracking enabled. Note the peak working set, and whether it ever decreases. If it doesn't, you have a leak.

Week 3. Audit your image and bitmap caches. Make sure their size budgets are derived from Runtime.getRuntime().maxMemory() rather than hard-coded. If you use Glide or Coil, verify the configured memory cache size against the device class — both libraries have device-aware defaults that you might have overridden.

Week 4. Audit any native libraries for unreleased allocations. If you have a JNI surface, run a representative workload under malloc_debug and look for leaks. This is the single biggest source of "memory looks fine in Android Studio but the app keeps dying" reports.

Week 5. Add a regression test for memory at the integration level. The new TRIGGER_TYPE_ANOMALY profiling is genuinely useful here — you can write a test that runs a long session and asserts that no MemoryLimiter exit occurred.

Week 6. Submit a build to internal testing, ideally targeting Android 17 in your compileSdk, and run the tests above against an actual 17 device or emulator image. If you're clean, you're ready for the stable.

One more thing on Play Console reporting#

Once Android 17 is stable, Play Console's "Excessive memory usage" vital is going to start surfacing the new limit kills as part of the bad behavior threshold. If your app crosses the threshold, it affects discoverability — Play will quietly stop recommending you. Indie devs who don't watch Play Console vitals weekly tend to discover this through a slow traffic decline they can't explain. Don't be that case. The vital is in Play Console → Quality → Android vitals → Excessive memory usage, and the threshold is currently 4% of sessions affected.

How Stora handles this#

When you push a build through Stora, the compliance engine runs static analysis on your APK or AAB to flag the patterns above before submission. It looks at your image cache configuration, your declared native libraries, and your service declarations, and flags configurations that are likely to hit the new memory ceiling on low-RAM devices. The build repair agent can suggest concrete fixes — switching a hard-coded cache size to a runtime-derived one, adding eviction policies, or rewriting service lifecycle hooks that hold onto memory across screens.

It's not a substitute for actually profiling your app on cheap hardware, but it's a much better starting point than discovering the cap by reading Play Console vitals after the fact.

Conclusion#

Android 17's per-app memory cap is not a dramatic policy shift, but it is one of those quiet platform changes that sorts apps into "ones that did the work" and "ones that didn't." Indie devs have six weeks before the stable rolls out. Wire up ApplicationExitInfo, profile on a cheap device, audit your caches, and submit early. The apps that handle the transition gracefully will benefit from the same Play Console signal that punishes the ones that don't — fewer ANR-class crashes, better vitals, more recommendations.

The good news is that fitting inside the new cap also means your app runs better on the devices that needed the cap in the first place. The work is the same work you should have been doing for the last billion users.