Android: OOM crashes in the RN bridge, worst on low-RAM devices #19

Open
opened 2026-09-01 16:52:54 +00:00 by mohlec · 1 comment
Owner

13 of the last 40 crash reports are heap exhaustion while marshalling an array across the
React Native bridge.

java.lang.OutOfMemoryError: Failed to allocate a NN byte allocation with ... until OOM,
target footprint 134217728, growth limit 134217728; giving up on allocation because
<1% of heap free after GC.
	at com.facebook.react.bridge.ReadableNativeArray.<init>(ReadableNativeArray.kt:23)

Distribution. Every report from one rugged Android 13/14 handset (128 MB heap growth
limit) is this signature, and it also reproduces on an SM-S938U / Android 16 at the 256 MB
limit. Hitting it at 256 MB suggests an oversized payload crossing the bridge, not merely a
weak device.

Notes and next steps.

  • android:largeHeap="true" is not currently set on <application>. Setting it would buy
    headroom on the low-RAM handsets, but that is mitigation, not a fix.
  • The real work is identifying what array is that large. Prime suspects are bulk
    channel/post sync on cold start and the dispatch board payload.
  • Ruled out: the ntfy SSE replay path. It builds notifications natively in
    NtfyPushService.handleEvent and never crosses the bridge, so it cannot be the source.

Observed: 2026-08-13 to 2026-08-28, app v1.0.0+1.

13 of the last 40 crash reports are heap exhaustion while marshalling an array across the React Native bridge. ``` java.lang.OutOfMemoryError: Failed to allocate a NN byte allocation with ... until OOM, target footprint 134217728, growth limit 134217728; giving up on allocation because <1% of heap free after GC. at com.facebook.react.bridge.ReadableNativeArray.<init>(ReadableNativeArray.kt:23) ``` **Distribution.** Every report from one rugged Android 13/14 handset (128 MB heap growth limit) is this signature, and it also reproduces on an SM-S938U / Android 16 at the 256 MB limit. Hitting it at 256 MB suggests an oversized payload crossing the bridge, not merely a weak device. **Notes and next steps.** - `android:largeHeap="true"` is not currently set on `<application>`. Setting it would buy headroom on the low-RAM handsets, but that is mitigation, not a fix. - The real work is identifying what array is that large. Prime suspects are bulk channel/post sync on cold start and the dispatch board payload. - Ruled out: the ntfy SSE replay path. It builds notifications natively in `NtfyPushService.handleEvent` and never crosses the bridge, so it cannot be the source. **Observed:** 2026-08-13 to 2026-08-28, app v1.0.0+1.
Author
Owner

Upstream context: hold off -- the rebase likely supersedes this

Two findings from reviewing the 55 upstream commits since the fork point (1e169c78).

1. The crash signature is legacy-bridge specific.
com.facebook.react.bridge.ReadableNativeArray.<init> is the old JS<->native bridge, which this
fork still uses (newArchEnabled=false, RN 0.77.3). Upstream b6fc85385 moves to RN 0.83.9 with
New Architecture enabled (newArchEnabled=true), where that array-copy path across the bridge
does not exist in the same form. Any fix written against the bridge here is likely to be discarded
at the rebase.

Recommendation: do not invest in a bridge-specific fix. android:largeHeap="true" remains
available as a cheap interim mitigation if the low-RAM handsets need relief before the rebase
lands, but it is mitigation only.

2. The upstream commit that looked like a direct fix does not apply to us.
30f18793f ("chunk fetchStatusByIds and updateAllUsersSince requests", MM-68226) chunks user-ID
payloads to avoid oversized requests and responses -- a plausible source of a very large array
crossing the bridge. But it targets servers with thousands of team members, and our install is
orders of magnitude below that scale. It is not the trigger here.

After the rebase, re-check whether OOM reports persist before reopening scope. If they do, the
payload hunt starts with cold-start channel/post sync and the dispatch board.

### Upstream context: hold off -- the rebase likely supersedes this Two findings from reviewing the 55 upstream commits since the fork point (`1e169c78`). **1. The crash signature is legacy-bridge specific.** `com.facebook.react.bridge.ReadableNativeArray.<init>` is the old JS<->native bridge, which this fork still uses (`newArchEnabled=false`, RN 0.77.3). Upstream `b6fc85385` moves to RN 0.83.9 with **New Architecture enabled** (`newArchEnabled=true`), where that array-copy path across the bridge does not exist in the same form. Any fix written against the bridge here is likely to be discarded at the rebase. Recommendation: do not invest in a bridge-specific fix. `android:largeHeap="true"` remains available as a cheap interim mitigation if the low-RAM handsets need relief before the rebase lands, but it is mitigation only. **2. The upstream commit that looked like a direct fix does not apply to us.** `30f18793f` ("chunk fetchStatusByIds and updateAllUsersSince requests", MM-68226) chunks user-ID payloads to avoid oversized requests and responses -- a plausible source of a very large array crossing the bridge. But it targets servers with *thousands* of team members, and our install is orders of magnitude below that scale. It is not the trigger here. After the rebase, re-check whether OOM reports persist before reopening scope. If they do, the payload hunt starts with cold-start channel/post sync and the dispatch board.
Sign in to join this conversation.
No labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
slash/slash-mobile#19
No description provided.