New Memory Limits in Android 17 Can Kill Your App Without a Crash Log
- 60% of MD5 Password Hashes Can Be Cracked in Under an Hour with a Single GPU
- Dirty Frag: Root Access on Every Major Linux Distribution — No Patch, No Warning
- Ubuntu 26.04 LTS (Resolute Raccoon): The Most Ambitious Ubuntu LTS in a Decade
- Proton Mail: Data Transferred to FBI Again!
- How Close Are Quantum Computers to Breaking RSA-2048?
- How to Prevent Ransomware Infection Risks?
- What is the best alternative to Microsoft Office?
New Memory Limits in Android 17 Can Kill Your App Without a Crash Log
Starting with Android 17, Google has introduced per-process memory limits based on a device’s total RAM. When an app exceeds its limit, the system can terminate the process immediately — without generating a standard crash stack trace. While most well-optimized apps will see no change, the new policy targets memory leaks, oversized image caches, and runaway foreground services before they destabilize the whole device.
Important: Memory limits are only enforced on a subset of Android 17 devices. However, every Android developer should prepare now — the enforcement footprint will expand over time.
How Are Restrictions Triggered?
Previously, Android’s Low Memory Killer (LMK) handled memory pressure by quietly pruning background processes first. If a single app consumed excessive memory, the system would reclaim memory from other cached apps, turning those apps’ next launch into a cold start with potential state loss.
Android 17 takes a more decisive approach: limits are set per process based on the device’s total RAM. Once a process exceeds that ceiling, the system terminates it directly. This prevents a misbehaving process from dragging down the entire multitasking experience.
Crucially, this is not a Java heap OutOfMemoryError — no clean stack trace appears in your crash platform. When a user reports “the app was killed by the system” with no associated crash, the new memory limiter is a prime suspect.
Detecting the Exit: ApplicationExitInfo
Since Android 11, ActivityManager.getHistoricalProcessExitReasons() lets you read why a process was killed. For memory-limit exits, the identifying fingerprint is a combination of two fields: the exit reason is REASON_OTHER and the description contains the string "MemoryLimiter:AnonSwap". Checking REASON_OTHER alone is insufficient — that code covers many other exit scenarios.
fun findMemoryLimiterExit(context: Context): Boolean {
val activityManager = context.getSystemService(ActivityManager::class.java)
val exits = activityManager.getHistoricalProcessExitReasons(
context.packageName,
0,
20
)
return exits.any { info ->
info.reason == ApplicationExitInfo.REASON_OTHER &&
info.description?.contains("MemoryLimiter:AnonSwap") == true
}
}
Local Reproduction with am memory-limiter
Android 17’s behavior change documentation introduces the am memory-limiter adb command, which lets you inspect and manually override memory limits for any running process — an essential tool for reproducing issues locally before they surface in production.
These commands only work on devices that have the memory limiter feature enabled. On devices without the limiter, commands will have no effect.
# 1. Get the PID for your app
adb shell pidof com.example.app
# 2. Check current memory limiter status
adb shell am memory-limiter status
# 3. Set a low limit (e.g. 300 MB) to trigger enforcement
adb shell am memory-limiter manual <pid> 300
# 4. Restore system defaults
adb shell am memory-limiter manual <pid> none
# 5. Remove all limits for the process
adb shell am memory-limiter manual <pid> max
# 6. Ignore specific or all UIDs
adb shell am memory-limiter ignore <uid>
adb shell am memory-limiter ignore all
adb shell am memory-limiter ignore none
Reduce Memory Footprint: Enable R8 Fully
Memory optimization should go beyond heap dumps. The code size, dead resources, and overly broad reflection-keep rules in your release APK all contribute to runtime resident memory. At a minimum, confirm that R8 shrinking and optimization are not accidentally disabled.
android {
buildTypes {
release {
isMinifyEnabled = true
isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
}
}
}
Avoid proguard-android.txt — it favours older compatibility behaviors and blocks key optimizations. It is also no longer supported in AGP 9. Use proguard-android-optimize.txt instead.
In gradle.properties, remove any line that disables R8 full mode:
# Delete this line from gradle.properties if it exists:
android.enableR8.fullMode=false
In proguard-rules.pro, avoid blanket switches such as -dontoptimize, -dontshrink, or -dontobfuscate. Scope keep rules to specific classes, fields, or annotations rather than entire packages.
Images & Memory Leaks
Images are one of the most underestimated sources of Android memory pressure. A PNG compressed to a few hundred kilobytes expands dramatically when decoded into ARGB_8888 — memory usage scales with pixel dimensions, not file size. Use Coil in Compose projects and Glide in View-based projects; never bypass these libraries with custom large-image loading. Always size decode output to match the display size of the target View or Composable.
For images without transparency requirements (profile photo thumbnails, list items), evaluate RGB_565, which uses half the per-pixel memory of ARGB_8888.
Duplicate bitmaps can be spotted directly in Android Studio Profiler’s Heap Dump view — it flags duplicates and shows image previews inline, making it straightforward to trace incorrect caching strategies.
Android Studio Panda 3 has added a dedicated LeakCanary profiler task that offloads memory leak analysis from the device to the development machine. Leak traces are contextualized with source code links, dramatically reducing time-to-fix for issues like uncleared Fragment bindings, unregistered listeners, and unreleased Compose DisposableEffects.
Proactively Release Cache with onTrimMemory()
When an app moves to the background, the system may reclaim memory. Because the system doesn’t know which objects are cheap to rebuild, implement onTrimMemory() in your Application class to guide that process manually.
class App : Application(), ComponentCallbacks2 {
override fun onTrimMemory(level: Int) {
if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
imageMemoryCache.clear()
videoPreviewCache.clear()
}
if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
searchResultCache.clear()
temporaryBufferPool.trim()
}
}
}
Note that in Android 14 and later, some older TRIM_* constants were deprecated and are no longer issued. Do not release non-recoverable business state here — drafts in progress, payment flow status, and user navigation choices should be handled through ViewModel/saved state, not cleared as cache.
Online Monitoring with ProfilingManager
Some memory issues only surface in production. Android 15 introduced ProfilingManager for in-app profiling callbacks; Android 17 expanded this with two new trigger types especially relevant to the memory limiter:
TRIGGER_TYPE_OOM — fires on the next app launch after an OutOfMemoryError crash, collecting a Java heap dump for upload.
TRIGGER_TYPE_ANOMALY — fires when the system detects a severe performance anomaly, including when the Android 17 memory limit is about to be breached. It triggers before the process is killed, giving you a heap dump at exactly the right moment.
val profilingManager = context.getSystemService(ProfilingManager::class.java)
val executor = Executors.newSingleThreadExecutor()
profilingManager.registerForAllProfilingResults(executor) { result ->
if (result.errorCode == ProfilingResult.ERROR_NONE) {
enqueueProfileUpload(result.resultFilePath)
} else {
logProfilingError(result.errorCode)
}
}
Heap dumps may contain in-memory object content. Consider sampling ratios, user consent, upload timing, file size limits, and retention policies before deploying to production. These are not ordinary logs.
Summary: Action Checklist
The most important items for Android 17 memory limit readiness:
✅ Developer Checklist
- Detect memory-limit exits via
REASON_OTHER+"MemoryLimiter:AnonSwap"inApplicationExitInfo - Reproduce locally with
adb shell am memory-limiter manual <pid> <mb> - Confirm
isMinifyEnabled = trueandproguard-android-optimize.txtin release builds - Remove
android.enableR8.fullMode=falsefromgradle.properties - Scope keep rules to specific classes — avoid
-dontoptimizeand blanket-keeprules - Implement
onTrimMemory()to release UI caches and preview buffers when backgrounded - Use Android Studio Panda’s LeakCanary profiler task to catch leaks in development
- Register
TRIGGER_TYPE_ANOMALYwithProfilingManagerfor production heap dump capture
