Skip to content

feat(android): Recover MemoryLimiter app exits on startup (JAVA-687) - #6111

Merged
0xadam-brown merged 8 commits into
mainfrom
feat/memory-limiter-integration
Sep 16, 2026
Merged

0xadam-brown merged 8 commits into
mainfrom
feat/memory-limiter-integration

Conversation

@0xadam-brown

@0xadam-brown 0xadam-brown commented Sep 14, 2026

Copy link
Copy Markdown
Member

📜 Description

Introduces a new MemoryLimiterIntegration that captures process deaths attributable to Android 17's new MemoryLimiter system service (see also here).

Process death info is extracted from ApplicationExitInfo on the next app launch. We then enrich it with persisted SDK state and send it to Relay as a fatal Sentry event.

Integration is experimental; is disabled by default; and is only available for Android API >= 37.

💡 Motivation and Context

Android 17 (API 37) introduced a new system service called MemoryLimiter that's responsible for killing app processes if they threaten to consume too much system memory.

The tricky part for us is that MemoryLimiter-caused process deaths don't involve an exception or a stack trace, making them invisible to our current instrumentation. This PR fills the gap by using ApplicationExitInfo to extract info about relevant exits on subsequent app launches. It does so by piggybacking on the ApplicationExitInfo processing pipeline used by ANRs and tombstones.

resolves: JAVA-687

Out of scope

This PR doesn't implement:

  • The reporting of non-fatal memory pressure or anomaly signals. (MemoryLimiter can throttle processes' memory allocations before killing them.) For now we only record process deaths.
  • Binding profiling artifacts to MemoryLimiter events, when available (e.g., trigger-based profiling generated by ProfilingManager).

We can revisit both going forward as we discover what will be most helpful to developers.

Basic flow

App process dies 
             |
             v                                                                                                                                                                                                                                                                                                                                             Android OS keeps a retained ApplicationExitInfo record                                                                                                                                                                                                                                                                                              
             |                                                                                                                                                                                                                                                                                                                                        
             v                                                                                                                                                                                                                                                                                                                                        
Next app launch initializes Sentry                                                                                                                                                                                                                                                                                                               
             |                                                                                                                                                                                                                                                                                                                                        
             +--> ApplicationExitInfo integrations (incl. MemoryLimiterIntegration) register a policy                                                                                                                                                                                                                                                                                                      
             |       ANR / Tombstone / MemoryLimiter                                                                                                                                                                                                                                                                                                  
             |                                                                                                                                                                                                                                                                                                                                        
             v                                                                                                                                                                                                                                                                                                                                        
Each integration creates its own ApplicationExitInfoHistoryDispatcher which...                                                                                                                                                                                                                                                                                                             
             |                                                                                                                                                                                                                                                                                                                                        
             +--> finds the latest matching exit, if any
             +--> optionally reports older matching exits                                                                                                                                                                                                                                                                                              
             +--> captures synthetic Sentry event(s) for all reported exits                                                                                                                                                                                                                                                                                                  
             |                                                                                                                                                                                                                                                                                                                                        
             v               
ApplicationExitInfoEventProcessor                                                                                                                                                                                                                                                                                                                
             |                                                                                                                                                                                                                                                                                                                                        
             +--> makes sure Sentry events for exits are bound to contextual data from previous process (not current).
             +--> attaches persisted scope/options when safe                                                                                                                                                                                                                                                                                            
             +--> keeps old historical exits lighter                                                                                                                                                                                                                                                                                                   
             |                                                                                                                                                                                                                                                                                                                                        
             v                                                                                                                                                                                                                                                                                                                                        
Envelope cache writes event and dedupe marker                                                                    

Each integration's ApplicationExitInfoHistoryDispatcher asks the ActivityManager for all recorded exits and does the following per policy:

Pick first matching exit as "latest"                                                                                                                                                                                                                                                                                                           
             |                                                                                                                                                                                                                                                                                                                                        
             +--> if too old or already reported: stop                                                                                                                                                                                                                                                                                                
             |                                                                                                                                                                                                                                                                                                                                        
             +--> if historical reporting enabled: report historical matches oldest -> newest  with shouldEnrich = false                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                                            
             |                                                                                                                                                                                                                                                                                                                                        
             v                                                                                                                                                                                                                                                                                                                                        
Report latest match with shouldEnrich = true 
             |
             v
Delegate to the policy to synthesize the base Sentry event  (to be enriched downstream if shouldEnrich = true)   

Note: there's definitely room for optimization here, in that each integration (when enabled) creates its own ApplicationExitInfoHistoryDispatcher, each dispatcher asks ActivityManager for the exit list, and each dispatcher scans the same exit list looking for matches against the policy it manages. But that's work for another day. Atm, this PR simply extends the pattern that already existed with ANRs and tombstones.

Screenshot

memory-limiter-3

I highlighted relevant info via the red boxes.

Screenshot URL: link

💚 How did you test it?

  1. Added plenty of integration and unit tests. Hooray.
  2. Had my clanker verify via our existing ANR and tombstone sample apps that enabling the MemoryLimiterIntegration doesn't interfere with current ANR or tombstone collection.
  3. I also created a sample app that verified everything downstream of actual OS functionality.

⚠️ W/r/t (3), current Android emulator images disable MemoryLimiter and I lack a physical device, so I haven't been able to test against actual OS behavior. That means we're depending on the Android 17 docs's accuracy when they claim that MemoryLimiter kills will be accompanied by REASON_OTHER and a "MemoryLimiter:AnonSwap" description. It'd be nice to see what actual OEM OS's return in the wild, however.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

🔮 Next steps

  1. Follow-on PR to be merged in same release as this one: For the sake of release health, recovered MemoryLimiter exits should mark the previous session abnormal at the recorded exit timestamp. (At present, the killed session is finalized as if it exited normally.) --> See chore(android): Mark MemoryLimiter sessions as having exited abnormally (JAVA-687) #6113.
  2. PR for the MemoryLimiterIntegration sample app.
  3. sentry-docs PR

Event path vs session path

The follow-on PR from (1) is needed because the current PR only covers the event path from the diagram below. The follow-on will cover the session path:

 Recovered process death on next launch                                                                                                                                                                                                                                                                                                           
             |                                                                                                                                                                                                                                                                                                                                        
             +--> Event path  (condensed from "Basic Flow" section above)                                                                                                                                                                                                                                                                                                                        
             |       dispatcher -> synthetic event -> backfill -> envelope                                                                                                                                                                                                                                                                            
             |                                                                                                                                                                                                                                                                                                                                        
             +--> Session path                                                                                                                                                                                                                                                                                                                        
                     previous session file -> abnormal/crashed end state 

@linear-code

linear-code Bot commented Sep 14, 2026

Copy link
Copy Markdown

JAVA-687

@github-actions

github-actions Bot commented Sep 14, 2026

Copy link
Copy Markdown
Contributor
Fails
🚫 Please consider adding a changelog entry for the next release.
Messages
📖 Do not forget to update Sentry-docs with your feature once the pull request gets approved.

Instructions and example for changelog

Please add an entry to CHANGELOG.md to the "Unreleased" section. Make sure the entry includes this PR's number.

Example:

## Unreleased

### Features

- Recover MemoryLimiter app exits on startup (JAVA-687) ([#6111](https://github.com/getsentry/sentry-java/pull/6111))

If none of the above apply, you can opt out of this check by adding #skip-changelog to the PR description or adding a skip-changelog label.

Generated by 🚫 dangerJS against 014e7c6

@sentry

sentry Bot commented Sep 14, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.56.0 (1) release

⚙️ sentry-android Build Distribution Settings

Comment thread sentry-android-core/api/sentry-android-core.api
Introduce a new MemoryLimiterIntegration that captures process deaths attributable to Android 17's new [MemoryLimiter](https://source.android.com/docs/core/perf/memory-limiter#process-monitoring) system service (see also [here](https://android-developers.googleblog.com/2026/06/prioritizing-memory-efficiency-steps-for-android-17.html)).

Process death info is extracted from ApplicationExitInfo on the next app launch. We then enrich it with persisted SDK state and send it to Relay as a fatal Sentry event.

Integration is experimental; is only available for Android API >= 37; and is disabled by default.
Add a data-driven test over every ApplicationExitInfo importance band,
including the default fallback, to lock the getProcessVisibility mapping.
… prefix

Match the "MemoryLimiter:" prefix instead of the full "MemoryLimiter:AnonSwap"
string. Per AOSP, AnonSwap is the only MemoryLimiter kill sub-reason on
Android 17, but the memory and swap limits it also tracks may start killing in a
future release; matching the namespace prefix keeps capturing those without a
code change, while the colon still anchors matching to the MemoryLimiter
namespace. The raw description is retained on the event mechanism.
@0xadam-brown
0xadam-brown force-pushed the feat/memory-limiter-integration branch from c38bf03 to cc0773d Compare September 14, 2026 12:35
@0xadam-brown
0xadam-brown marked this pull request as ready for review September 14, 2026 12:51
@0xadam-brown 0xadam-brown added the deep-dive PR needs a thorough review of design, behavior, and edge cases label Sep 14, 2026

@runningcode runningcode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The overall pattern looks good to me! just some comments.

@markushi markushi left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking good, left a few minor comments. I'm holding off to approving it for now, due to the follow up PR.

Also renames "importance" -> "process_importance" and "process_visibility" -> "memory_limit_class" to better match the language used in the MemoryLimiter docs here: https://source.android.com/docs/core/perf/memory-limiter#process-monitoring
… int from message

The deprecated process importance info was unneeded as it's only present pre-API 37, while our integration is only available starting on API 37.

The process importance int in the message ended up being redundant, b/c the error detail view shows a separarate listing for that int immediately below the importance string.
@0xadam-brown
0xadam-brown merged commit bc5c4c9 into main Sep 16, 2026
96 of 98 checks passed
@0xadam-brown
0xadam-brown deleted the feat/memory-limiter-integration branch September 16, 2026 12:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-dive PR needs a thorough review of design, behavior, and edge cases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants