Skip to content

Repeated Activity recreation / switching leads to heavy GC pressure, OOM or process kill #10989

@jdw89-123

Description

@jdw89-123

Android framework version

net10.0-android

Affected platform version

.NET10 android on physical device, Mono runtime

Description

We are seeing what appears to be excessive managed and native memory growth when repeatedly switching between dynamically created Android activities.

In our production app, we use many dynamic activities and frequent activity transitions. Activities are often recreated. To isolate the behavior, I built a minimal reproduction with two simple activities (ActivityA and ActivityB) that navigate back and forth. The UI is created entirely in code.

Minimal reproduction:
https://github.com/jdw89-123/activitiesFillHeap


Expected behavior

Repeated activity recreation and activity switching should remain stable and should not drive the managed or native heap into repeated full-heap GC / OOM scenarios.


Actual behavior

After repeated navigation between activities, the app shows:

  • continuous growth of managed and native memory
  • heavy GC pressure
  • UI stalls
  • eventually either:
    • OutOfMemoryError or
    • ANR or
    • the process gets killed by the system

Android Studio Live Telemetry shows rapid growth in both native and Java heap:

Image

Managed heap analysis

Managed heap dumps (via dotnet-gcdump) show a continuous increase in the number of specific object types.

The object count:

  • does not decrease after GC
  • does not stabilize at a plateau
  • instead grows steadily with repeated activity transitions

Affected types:

  • Android.Runtime.IdentityHashTargets
  • WeakReference<Java.Interop.IJavaPeerable>
Image

Additional observation (explicit GC)

Based on the discussion in the following PR:
#10856

I experimented with triggering an explicit GC in OnCreate of the activity:

System.GC.WaitForPendingFinalizers();
System.GC.Collect();

Notes

The reproduction is intentionally minimal, but it shows the same pattern we are seeing in a much larger production application.

What makes this particularly suspicious is that even this small repro appears sufficient to push both the managed and native heap into a steadily growing state that does not recover over time.

Additionally, the number of view elements on the activities appears to influence how quickly the issue manifests. Increasing the number of UI elements reduces the number of activity transitions required to reach the critical state, suggesting that the problem is related to the amount of UI objects created and destroyed during activity recreation.

Steps to Reproduce

  1. Clone the minimal reproduction repository:
    https://github.com/jdw89-123/activitiesFillHeap

  2. Build and deploy the app to a physical Android device.

  3. Launch the app.

  4. Repeatedly press the button to switch between ActivityA and ActivityB.

  5. Continue switching activities continuously.

After approximately 2–3 minutes of rapid switching, the app enters a critical state and eventually crashes (OutOfMemoryError or process kill).

Did you find any workaround?

no

Relevant log output

Without explicit GC, the repro repeatedly reaches the managed heap limit and eventually throws `OutOfMemoryError`, including failures for very small allocations:

- `Clamp target GC heap from 152MB to 128MB`
- `0% free, 128MB/128MB`
- `Forcing collection of SoftReferences for 32B allocation`
- `Throwing OutOfMemoryError "Failed to allocate a 32 byte allocation ..."`
- stack includes `crc64408c12b9499ddfa7.ActivityA.n_onCreate(Native method)`

With explicit GC, the failure is delayed but not prevented. The app still reaches the heap limit and eventually becomes unresponsive or gets terminated:

- `Activity destroy timeout for ... ActivityB`
- `Clamp target GC heap from 151MB to 128MB`
- `0% free, 127MB/128MB`
- `WaitForGcToComplete blocked Alloc on HeapTrim for 1.603s`
- later: `Throwing OutOfMemoryError "Failed to allocate a 16400 byte allocation ..."`

These logs suggest that explicit GC changes the timing, but not the underlying failure mode.

Full logcats:
logcat1.txt
logcat2.txt


UPDATE: .NET 8 vs .NET 10

I compared the same reproduction on .NET 8 and .NET 10, with explicit GC enabled in OnCreate of both activities:

System.GC.WaitForPendingFinalizers();
System.GC.Collect();

The difference is significant.

Under .NET 8, the application behaves as expected and remains stable during repeated activity switching.
Image

Under .NET 10, memory consumption grows aggressively under the same conditions. Even with explicit GC, the profiler shows clearly higher and continuously increasing memory usage compared to .NET 8.
Image

This suggests that the behavior is not only related to the general activity recreation pattern itself, but that there may also be a regression or behavioral change between .NET 8 and .NET 10 in how Android-managed/native interop objects are retained or released.

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: App RuntimeIssues in `libmonodroid.so`.needs-triageIssues that need to be assigned.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions