[Mono.Android] Reduce JNI overhead in the CoreCLR GC bridge#12040
Open
simonrozsival wants to merge 1 commit into
Open
[Mono.Android] Reduce JNI overhead in the CoreCLR GC bridge#12040simonrozsival wants to merge 1 commit into
simonrozsival wants to merge 1 commit into
Conversation
Make JNI usage during CoreCLR GC bridge processing cheaper and more robust. All changes are in the CoreCLR/NativeAOT shared native host (`src/native/clr/host`). * Cache the `mono.android.IGCUserPeer` interface method IDs (`monodroidAddReference` / `monodroidClearReferences`) once at runtime init instead of doing a `GetObjectClass` + `GetMethodID` lookup for every reference edge and every cleared node during bridge processing. This mirrors how the NativeAOT host already caches the `net.dot.jni.GCUserPeerable` method IDs, roughly halves the JNI calls per edge, and removes a transient local reference per edge. The `IsInstanceOf` guard preserves the previous "missing method" fallback and the existing abort-on-failure behaviour. * Reserve JNI local reference capacity up front for the temporary peers created for empty strongly connected components, so that a large number of such SCCs cannot overflow the JNI local reference table (which only guarantees 16 slots by default). * Delete the `jclass` local reference obtained in `GCBridge::log_handle_context`. It was leaked on the long-lived, attached bridge thread on every logged handle when GC spew is enabled. * Fix the inverted GC-spew guard in `log_weak_ref_collected` so the "was collected by a Java GC" message is emitted when GC spew is enabled (matching every sibling logging helper) rather than when it is disabled. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 8e3188a8-82e3-4a21-bf4c-257b7ad9752c
Contributor
There was a problem hiding this comment.
Pull request overview
This PR reduces JNI overhead and improves local-reference hygiene in the CoreCLR GC bridge host (shared with the NativeAOT host), focusing on making bridge processing more efficient and robust without changing GC liveness semantics.
Changes:
- Cache
mono.android.IGCUserPeerinterface method IDs once at runtime init, avoiding per-edgeGetObjectClass/GetMethodIDand associated local refs during bridge cycles. - Proactively call
EnsureLocalCapacity()to accommodate temporary peers created for SCCs withCount == 0, mitigating JNI local-ref-table overflow risk. - Fix local-ref management and GC-spew logging guards (
DeleteLocalRef()forGetObjectClass()inlog_handle_context, and correct thegc_spew_enabled()early-return logic).
Show a summary per file
| File | Description |
|---|---|
| src/native/clr/include/host/bridge-processing-shared.hh | Adds cached IGCUserPeer JNI handles (class + method IDs) to support per-edge JNI call reductions. |
| src/native/clr/host/gc-bridge.cc | Deletes the jclass local ref created by GetObjectClass() in log_handle_context, preventing a local-ref leak on the bridge thread when logging. |
| src/native/clr/host/bridge-processing.cc | Initializes cached IGCUserPeer method IDs, reserves local-ref capacity for temporary peers, and switches reference add/clear to cached interface dispatch with an IsInstanceOf guard; fixes GC-spew guard inversion in log_weak_ref_collected. |
Copilot's findings
- Files reviewed: 3/3 changed files
- Comments generated: 0
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Note
This pull request was created with the assistance of GitHub Copilot. The code and description were AI-generated and reviewed by the author.
Summary
Reduces JNI overhead and fixes local-reference hygiene in the CoreCLR GC
bridge host (
src/native/clr/host, shared with the NativeAOT host). Nobehavioral change to which objects survive a collection — these are
efficiency/robustness improvements to how bridge processing talks to JNI.
Changes
Cache
mono.android.IGCUserPeermethod IDs.add_reference/clear_referencespreviously did aGetObjectClass+GetMethodIDlookup (plus a transient local reference) for every reference edge
and every cleared node, on every bridge cycle. The interface method IDs
(
monodroidAddReference/monodroidClearReferences) are now resolvedonce at runtime init and dispatched via an
IsInstanceOfguard. Thismirrors how the NativeAOT host already caches the
net.dot.jni.GCUserPeerablemethod IDs, roughly halves the JNI callsper edge, and removes a per-edge local reference. The
IsInstanceOfguard preserves the previous "missing method" fallback and the
add_circular_referencesabort-on-failure semantics.Reserve JNI local capacity for empty-SCC temporary peers. Each
strongly connected component with no IGCUserPeers is represented by a
temporary peer held as a JNI local reference until all cross references
are added.
prepare_for_java_collectionnow reserves local referencecapacity up front so a large number of such SCCs cannot overflow the
JNI local reference table (which only guarantees 16 slots by default).
Fix a local-reference leak in
GCBridge::log_handle_context. ThejclassfromGetObjectClasswas never deleted, leaking one local perlogged handle on the long-lived, attached bridge thread whenever GC
spew is enabled.
Fix the inverted GC-spew guard in
log_weak_ref_collected. The"was collected by a Java GC" message was emitted when GC spew was
disabled and suppressed when it was enabled — the opposite of every
sibling logging helper (e.g.
log_gc_summary).Testing
Built the CoreCLR
android-arm64runtime with these changes and ran thefull
Mono.Android.NET-Testssuite on an API 36arm64-v8aemulator with-p:UseMonoRuntime=false(CoreCLR):debug.mono.log=gcenabled, the GC bridge ran 12 times during thesuite (graphs up to ~389 objects, tens of objects resurrected per cycle),
exercising the cached-method-ID
add_reference/clear_referencespathsand the temporary-peer capacity path, with no CheckJNI / JNI errors /
local-reference-table overflows (CheckJNI is active for the debuggable
test app).
JavaObjectTest.UnregisterFromRuntime,a registered-peer count assertion) flaked once under heavy GC-spew logging
and passed on re-run; it is not affected by these changes.