Summary
This issue records an experiment evaluating whether the CoreCLR Android GC bridge can avoid synchronously invoking java.lang.Runtime.gc() and instead defer Java peer reclamation until ART runs collection naturally.
The result is promising but not yet production-ready:
- Deferred collection worked for an 80-minute stress run containing approximately 349,000 Android Callable Wrappers (ACWs).
- ART naturally reclaimed JNI weak global references (WREFs), allowing the corresponding managed handles to be retired.
- The WREF table remained below ART's 51,200-entry limit.
- Avoiding synchronous
Runtime.gc() reduced typical bridge time and improved frame pacing in a separate rendering benchmark.
- The trade-offs were higher memory use, larger retained WREF populations, and worse bridge-time outliers.
This is an investigation report, not yet a proposal to change the product implementation.
Related context:
Prototype design
The prototype changes the bridge lifecycle as follows:
- Unreachable Java peers are converted from JNI strong global references (GREFs) to WREFs.
- The bridge completes without calling
Runtime.gc().
- WREFs remain registered across subsequent bridge rounds.
- Later rounds probe each WREF using
NewLocalRef().
- If ART cleared the WREF, the WREF is deleted and its managed handle is released.
- Live WREFs are temporarily strengthened only when needed to rebuild the current bridge graph.
A process-wide sweep handles WREFs that are no longer part of the current CoreCLR cross-reference graph.
For comparison, an adb property can enable the old behavior before each sweep:
debug.net.gcbridge.explicit_gc=0 # natural ART collection
debug.net.gcbridge.explicit_gc=1 # invoke Runtime.gc()
The property is read during every bridge round, so both modes use the same APK and runtime implementation.
Stress workload
The test application repeatedly:
- creates a random component containing 1–1,000 custom ACWs;
- connects the component into a strongly connected ring with additional random edges;
- waits 10 ms between individual ACW allocations;
- drops the only root after completing the component;
- records managed GC counts, managed heap data, Java heap data, JNI GREF count, RSS, bridge population, reclamation, and phase timings.
Both configurations ran for 10, 20, 40, and finally 80 minutes on the same host-accelerated arm64 emulator using its default ART heap policy. All runs completed without a WREF overflow or fatal runtime error.
80-minute A/B result
| Metric |
Natural ART collection |
Explicit Runtime.gc() |
| ACWs allocated |
348,659 |
347,124 |
| Bridge rounds |
134 |
147 |
| Maximum WREFs |
35,510 |
7,616 |
| Final WREFs |
27,052 |
2,437 |
| Handles reclaimed |
321,609 |
343,266 |
| Median bridge algorithm time |
9.9 ms |
17.3 ms |
| 95th-percentile bridge algorithm time |
45.8 ms |
21.3 ms |
| Maximum bridge algorithm time |
77.4 ms |
22.6 ms |
Median Runtime.gc() time |
— |
12.8 ms |
| Final managed heap size |
73.5 MiB |
8.8 MiB |
| Final RSS |
255.0 MiB |
182.1 MiB |
Throughput was intentionally paced by the test and was effectively identical.
Natural collection produced a repeating sawtooth pattern: WREFs accumulated and then fell when ART independently collected their Java referents. The bridge observed 21 reclamation cycles during the natural 80-minute run.


Natural ART collection is marked at the bridge round that first observed and reclaimed cleared WREFs. Explicit collection timestamps come directly from ART's Explicit concurrent mark compact GC log entries.
Rendering benchmark result
A separate five-minute host-GPU emulator comparison used the application from runtime#131370:
| Metric |
Natural ART collection |
Explicit Runtime.gc() |
| Average bridge time |
3.1 ms |
11.3 ms |
| Maximum bridge time |
18 ms |
37 ms |
| Frames below 55 FPS |
2 |
7 |
| Maximum WREFs |
2,465 |
641 |
This indicates that removing synchronous ART collection can provide a meaningful typical-latency and frame-pacing improvement. A physical Samsung S23 test also confirmed that ART can naturally clear deferred bridge WREFs under a real 60 FPS workload, although collection was infrequent when Java heap pressure was low.
What we learned
The lifecycle is technically feasible
Keeping peers as WREFs across bridge rounds works. ART can clear them naturally, and the bridge can later detect that state and safely retire the associated managed handles.
It removes a significant synchronous cost
Runtime.gc() directly adds ART collection time to the bridge operation. Avoiding it improved typical bridge duration in both the stress workload and the rendering benchmark.
It exchanges pause time for memory and tail latency
Without explicit collection, more WREFs and managed contexts remain registered. Process-wide probing becomes more expensive as that population grows. The natural run therefore had a better median but substantially worse outliers and higher RSS.
The current diagnostics deliberately scan every registered reference and emit detailed logcat records. They contribute to measured latency, so production measurements without this instrumentation should be better. The underlying sweep is still proportional to the retained WREF population, however.
Natural ART scheduling is not a safety guarantee
ART collected often enough in this stress workload, but applications with little Java allocation pressure can retain WREFs for much longer. The prototype cannot guarantee that ART will collect before its fixed 51,200-entry WREF table fills.
Requirements before considering a product change
- Provide a hard overflow-prevention strategy instead of relying exclusively on ART scheduling.
- Make WREF cleanup incremental or otherwise bound the work performed during one bridge round.
- Validate resurrection and peer-reconnection behavior.
- Repeat realistic application and physical-device measurements without detailed diagnostic scanning/logging.
- Evaluate memory and frame-time percentiles across a wider range of workloads and ART implementations.
Current conclusion
Deferred ART collection is worth pursuing. It successfully moves expensive Java collection out of the normal GC bridge path and improves typical bridge latency and frame pacing.
It is not yet safe to ship as-is because reclamation timing is controlled by ART, memory retention is higher, and the current design has no guaranteed protection against WREF-table exhaustion.
Summary
This issue records an experiment evaluating whether the CoreCLR Android GC bridge can avoid synchronously invoking
java.lang.Runtime.gc()and instead defer Java peer reclamation until ART runs collection naturally.The result is promising but not yet production-ready:
Runtime.gc()reduced typical bridge time and improved frame pacing in a separate rendering benchmark.This is an investigation report, not yet a proposal to change the product implementation.
Related context:
Prototype design
The prototype changes the bridge lifecycle as follows:
Runtime.gc().NewLocalRef().A process-wide sweep handles WREFs that are no longer part of the current CoreCLR cross-reference graph.
For comparison, an adb property can enable the old behavior before each sweep:
The property is read during every bridge round, so both modes use the same APK and runtime implementation.
Stress workload
The test application repeatedly:
Both configurations ran for 10, 20, 40, and finally 80 minutes on the same host-accelerated arm64 emulator using its default ART heap policy. All runs completed without a WREF overflow or fatal runtime error.
80-minute A/B result
Runtime.gc()Runtime.gc()timeThroughput was intentionally paced by the test and was effectively identical.
Natural collection produced a repeating sawtooth pattern: WREFs accumulated and then fell when ART independently collected their Java referents. The bridge observed 21 reclamation cycles during the natural 80-minute run.
Natural ART collection is marked at the bridge round that first observed and reclaimed cleared WREFs. Explicit collection timestamps come directly from ART's
Explicit concurrent mark compact GClog entries.Rendering benchmark result
A separate five-minute host-GPU emulator comparison used the application from runtime#131370:
Runtime.gc()This indicates that removing synchronous ART collection can provide a meaningful typical-latency and frame-pacing improvement. A physical Samsung S23 test also confirmed that ART can naturally clear deferred bridge WREFs under a real 60 FPS workload, although collection was infrequent when Java heap pressure was low.
What we learned
The lifecycle is technically feasible
Keeping peers as WREFs across bridge rounds works. ART can clear them naturally, and the bridge can later detect that state and safely retire the associated managed handles.
It removes a significant synchronous cost
Runtime.gc()directly adds ART collection time to the bridge operation. Avoiding it improved typical bridge duration in both the stress workload and the rendering benchmark.It exchanges pause time for memory and tail latency
Without explicit collection, more WREFs and managed contexts remain registered. Process-wide probing becomes more expensive as that population grows. The natural run therefore had a better median but substantially worse outliers and higher RSS.
The current diagnostics deliberately scan every registered reference and emit detailed logcat records. They contribute to measured latency, so production measurements without this instrumentation should be better. The underlying sweep is still proportional to the retained WREF population, however.
Natural ART scheduling is not a safety guarantee
ART collected often enough in this stress workload, but applications with little Java allocation pressure can retain WREFs for much longer. The prototype cannot guarantee that ART will collect before its fixed 51,200-entry WREF table fills.
Requirements before considering a product change
Current conclusion
Deferred ART collection is worth pursuing. It successfully moves expensive Java collection out of the normal GC bridge path and improves typical bridge latency and frame pacing.
It is not yet safe to ship as-is because reclamation timing is controlled by ART, memory retention is higher, and the current design has no guaranteed protection against WREF-table exhaustion.