diff --git a/src/Java.Interop/Java.Interop/IJavaPeerable.cs b/src/Java.Interop/Java.Interop/IJavaPeerable.cs
index 612744b10..a1888e7e8 100644
--- a/src/Java.Interop/Java.Interop/IJavaPeerable.cs
+++ b/src/Java.Interop/Java.Interop/IJavaPeerable.cs
@@ -37,6 +37,8 @@ public interface IJavaPeerable : IDisposable
void Disposed ();
///
void Finalized ();
+
+ IntPtr JniObjectReferenceControlBlock => IntPtr.Zero;
}
}
diff --git a/src/Java.Interop/Java.Interop/JavaException.cs b/src/Java.Interop/Java.Interop/JavaException.cs
index b07735b5a..765afc4ea 100644
--- a/src/Java.Interop/Java.Interop/JavaException.cs
+++ b/src/Java.Interop/Java.Interop/JavaException.cs
@@ -5,7 +5,7 @@
namespace Java.Interop
{
[JniTypeSignature (JniTypeName, GenerateJavaPeer=false)]
- unsafe public class JavaException : Exception, IJavaPeerable
+ unsafe public partial class JavaException : Exception, IJavaPeerable
{
internal const string JniTypeName = "java/lang/Throwable";
readonly static JniPeerMembers _members = new JniPeerMembers (JniTypeName, typeof (JavaException));
@@ -18,13 +18,7 @@ unsafe public class JavaException : Exception, IJavaPeerable
JniObjectReference reference;
#endif // FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
- IntPtr handle;
- JniObjectReferenceType handle_type;
- #pragma warning disable 0169
- // Used by JavaInteropGCBridge
- IntPtr weak_handle;
- int refs_added;
- #pragma warning restore 0169
+ unsafe JniObjectReferenceControlBlock* jniObjectReferenceControlBlock;
#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
protected static readonly JniObjectReference* InvalidJniObjectReference = null;
@@ -106,7 +100,11 @@ public JniObjectReference PeerReference {
return reference;
#endif // FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
- return new JniObjectReference (handle, handle_type);
+ var c = jniObjectReferenceControlBlock;
+ if (c == null) {
+ return default;
+ }
+ return new JniObjectReference (c->handle, (JniObjectReferenceType) c->handle_type);
#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
}
}
@@ -137,8 +135,13 @@ protected void SetPeerReference (ref JniObjectReference reference, JniObjectRefe
this.reference = reference;
#endif // FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
- this.handle = reference.Handle;
- this.handle_type = reference.Type;
+ var c = jniObjectReferenceControlBlock;
+ if (c == null) {
+ c = jniObjectReferenceControlBlock =
+ Java.Interop.JniObjectReferenceControlBlock.Alloc ();
+ }
+ c->handle = reference.Handle;
+ c->handle_type = (int) reference.Type;
#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
JniObjectReference.Dispose (ref reference, options);
@@ -260,11 +263,13 @@ protected void SetJavaStackTrace (JniObjectReference peerReferenceOverride = def
void IJavaPeerable.Disposed ()
{
+ JniManagedPeerState |= Disposed;
Dispose (disposing: true);
}
void IJavaPeerable.Finalized ()
{
+ JniManagedPeerState |= Disposed;
Dispose (disposing: false);
}
@@ -281,7 +286,15 @@ void IJavaPeerable.SetJniManagedPeerState (JniManagedPeerStates value)
void IJavaPeerable.SetPeerReference (JniObjectReference reference)
{
SetPeerReference (ref reference, JniObjectReferenceOptions.Copy);
+#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
+ if (!reference.IsValid && JniManagedPeerState.HasFlag (Disposed)) {
+ Java.Interop.JniObjectReferenceControlBlock.Free (ref jniObjectReferenceControlBlock);
+ }
+#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
}
+
+ IntPtr IJavaPeerable.JniObjectReferenceControlBlock =>
+ (IntPtr) jniObjectReferenceControlBlock;
}
}
diff --git a/src/Java.Interop/Java.Interop/JavaObject.cs b/src/Java.Interop/Java.Interop/JavaObject.cs
index 02a69e061..335c9f6e1 100644
--- a/src/Java.Interop/Java.Interop/JavaObject.cs
+++ b/src/Java.Interop/Java.Interop/JavaObject.cs
@@ -8,7 +8,7 @@ namespace Java.Interop
{
[JniTypeSignature ("java/lang/Object", GenerateJavaPeer=false)]
[Serializable]
- unsafe public class JavaObject : IJavaPeerable
+ unsafe public partial class JavaObject : IJavaPeerable
{
internal const DynamicallyAccessedMemberTypes Constructors = DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors;
@@ -25,13 +25,7 @@ unsafe public class JavaObject : IJavaPeerable
[NonSerialized] JniObjectReference reference;
#endif // FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
- [NonSerialized] IntPtr handle;
- [NonSerialized] JniObjectReferenceType handle_type;
- #pragma warning disable 0169
- // Used by JavaInteropGCBridge
- [NonSerialized] IntPtr weak_handle;
- [NonSerialized] int refs_added;
- #pragma warning restore 0169
+ unsafe JniObjectReferenceControlBlock* jniObjectReferenceControlBlock;
#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
protected static readonly JniObjectReference* InvalidJniObjectReference = null;
@@ -41,13 +35,17 @@ unsafe public class JavaObject : IJavaPeerable
JniEnvironment.Runtime.ValueManager.FinalizePeer (this);
}
- public JniObjectReference PeerReference {
+ public unsafe JniObjectReference PeerReference {
get {
#if FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
return reference;
#endif // FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
- return new JniObjectReference (handle, handle_type);
+ var c = jniObjectReferenceControlBlock;
+ if (c == null) {
+ return default;
+ }
+ return new JniObjectReference (c->handle, (JniObjectReferenceType) c->handle_type);
#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
}
}
@@ -91,8 +89,13 @@ protected void SetPeerReference (ref JniObjectReference reference, JniObjectRefe
this.reference = reference;
#endif // FEATURE_JNIOBJECTREFERENCE_SAFEHANDLES
#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
- this.handle = reference.Handle;
- this.handle_type = reference.Type;
+ var c = jniObjectReferenceControlBlock;
+ if (c == null) {
+ c = jniObjectReferenceControlBlock =
+ Java.Interop.JniObjectReferenceControlBlock.Alloc ();
+ }
+ c->handle = reference.Handle;
+ c->handle_type = (int) reference.Type;
#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
JniObjectReference.Dispose (ref reference, options);
@@ -147,11 +150,13 @@ public override unsafe int GetHashCode ()
void IJavaPeerable.Disposed ()
{
+ managedPeerState |= Disposed;
Dispose (disposing: true);
}
void IJavaPeerable.Finalized ()
{
+ managedPeerState |= Disposed;
Dispose (disposing: false);
}
@@ -168,7 +173,16 @@ void IJavaPeerable.SetJniManagedPeerState (JniManagedPeerStates value)
void IJavaPeerable.SetPeerReference (JniObjectReference reference)
{
SetPeerReference (ref reference, JniObjectReferenceOptions.Copy);
+
+#if FEATURE_JNIOBJECTREFERENCE_INTPTRS
+ if (!reference.IsValid && managedPeerState.HasFlag (Disposed)) {
+ Java.Interop.JniObjectReferenceControlBlock.Free (ref jniObjectReferenceControlBlock);
+ }
+#endif // FEATURE_JNIOBJECTREFERENCE_INTPTRS
}
+
+ IntPtr IJavaPeerable.JniObjectReferenceControlBlock =>
+ (IntPtr) jniObjectReferenceControlBlock;
}
}
diff --git a/src/Java.Interop/Java.Interop/JniManagedPeerStates.cs b/src/Java.Interop/Java.Interop/JniManagedPeerStates.cs
index 765fb0b2d..b4f1fba3c 100644
--- a/src/Java.Interop/Java.Interop/JniManagedPeerStates.cs
+++ b/src/Java.Interop/Java.Interop/JniManagedPeerStates.cs
@@ -11,4 +11,12 @@ public enum JniManagedPeerStates {
///
Replaceable = (1 << 1),
}
+
+ partial class JavaObject {
+ const JniManagedPeerStates Disposed = (JniManagedPeerStates) (1 << 2);
+ }
+
+ partial class JavaException {
+ const JniManagedPeerStates Disposed = (JniManagedPeerStates) (1 << 2);
+ }
}
diff --git a/src/Java.Interop/Java.Interop/JniObjectReferenceControlBlock.cs b/src/Java.Interop/Java.Interop/JniObjectReferenceControlBlock.cs
new file mode 100644
index 000000000..348e08cbd
--- /dev/null
+++ b/src/Java.Interop/Java.Interop/JniObjectReferenceControlBlock.cs
@@ -0,0 +1,27 @@
+using System;
+using System.Runtime.InteropServices;
+
+namespace Java.Interop;
+
+internal struct JniObjectReferenceControlBlock {
+ public IntPtr handle;
+ public int handle_type;
+ public IntPtr weak_handle;
+ public int refs_added;
+
+ public static readonly int Size = Marshal.SizeOf();
+
+ public static unsafe JniObjectReferenceControlBlock* Alloc ()
+ {
+ return (JniObjectReferenceControlBlock*) NativeMemory.AllocZeroed (1, (uint) Size);
+ }
+
+ public static unsafe void Free (ref JniObjectReferenceControlBlock* value)
+ {
+ if (value == null) {
+ return;
+ }
+ NativeMemory.Free (value);
+ value = null;
+ }
+}
diff --git a/src/Java.Interop/PublicAPI.Unshipped.txt b/src/Java.Interop/PublicAPI.Unshipped.txt
index 90cde5ae6..90fda8ff8 100644
--- a/src/Java.Interop/PublicAPI.Unshipped.txt
+++ b/src/Java.Interop/PublicAPI.Unshipped.txt
@@ -11,3 +11,4 @@ Java.Interop.JniRuntime.JniTypeManager.GetInvokerType(System.Type! type) -> Syst
Java.Interop.JniRuntime.JniValueManager.GetPeer(Java.Interop.JniObjectReference reference, System.Type? targetType = null) -> Java.Interop.IJavaPeerable?
Java.Interop.JniTypeSignatureAttribute.InvokerType.get -> System.Type?
Java.Interop.JniTypeSignatureAttribute.InvokerType.set -> void
+Java.Interop.IJavaPeerable.JniObjectReferenceControlBlock.get -> nint
diff --git a/src/Java.Runtime.Environment/Java.Interop/JavaBridgedValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/JavaBridgedValueManager.cs
new file mode 100644
index 000000000..0c17d51bc
--- /dev/null
+++ b/src/Java.Runtime.Environment/Java.Interop/JavaBridgedValueManager.cs
@@ -0,0 +1,319 @@
+using System;
+using System.Collections.Generic;
+using System.Collections.ObjectModel;
+using System.Diagnostics;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.IO;
+using System.Linq;
+using System.Reflection;
+using System.Runtime.CompilerServices;
+using System.Runtime.InteropServices;
+using System.Runtime.InteropServices.Java;
+using System.Threading;
+
+using Java.Interop;
+
+namespace System.Runtime.InteropServices.Java {
+ // https://github.com/dotnet/runtime/issues/115506
+
+ public struct ComponentCrossReference {
+ public nint SourceGroupIndex;
+ public nint DestinationGroupIndex;
+ }
+
+ public unsafe struct StronglyConnectedComponent {
+ public int IsAlive;
+ public nint Count;
+ public System.IntPtr* Context;
+ }
+
+ public unsafe struct MarkCrossReferences {
+ public nint ComponentsLen;
+ public StronglyConnectedComponent* Components;
+ public nint CrossReferencesLen;
+ public ComponentCrossReference* CrossReferences;
+ }
+
+ static class JavaMarshal {
+ public static unsafe void Initialize(delegate* unmanaged markCrossReferences) =>
+ throw null!;
+
+ public static GCHandle CreateReferenceTrackingHandle(object obj, IntPtr context) =>
+ throw null!;
+
+ public static IntPtr GetContext(GCHandle obj) =>
+ throw null!;
+
+ public static unsafe void ReleaseMarkCrossReferenceResources(MarkCrossReferences* crossReferences) =>
+ throw null!;
+ }
+}
+
+namespace Java.Interop {
+
+ class JavaBridgedValueManager : JniRuntime.JniValueManager
+ {
+ Dictionary>? RegisteredInstances = new ();
+
+ internal unsafe JavaBridgedValueManager ()
+ {
+ JavaMarshal.Initialize (&MarkCrossReferences);
+ }
+
+ public override void WaitForGCBridgeProcessing ()
+ {
+ }
+
+ public override void CollectPeers ()
+ {
+ if (RegisteredInstances == null)
+ throw new ObjectDisposedException (nameof (ManagedValueManager));
+
+ var peers = new List ();
+
+ lock (RegisteredInstances) {
+ foreach (var ps in RegisteredInstances.Values) {
+ foreach (var p in ps) {
+ peers.Add (p);
+ }
+ }
+ RegisteredInstances.Clear ();
+ }
+ List? exceptions = null;
+ foreach (var peer in peers) {
+ try {
+ if (peer.Target is IDisposable disposable)
+ disposable.Dispose ();
+ }
+ catch (Exception e) {
+ exceptions = exceptions ?? new List ();
+ exceptions.Add (e);
+ }
+ }
+ if (exceptions != null)
+ throw new AggregateException ("Exceptions while collecting peers.", exceptions);
+ }
+
+ public override void AddPeer (IJavaPeerable value)
+ {
+ if (RegisteredInstances == null)
+ throw new ObjectDisposedException (nameof (ManagedValueManager));
+
+ var r = value.PeerReference;
+ if (!r.IsValid)
+ throw new ObjectDisposedException (value.GetType ().FullName);
+
+ if (r.Type != JniObjectReferenceType.Global) {
+ value.SetPeerReference (r.NewGlobalRef ());
+ JniObjectReference.Dispose (ref r, JniObjectReferenceOptions.CopyAndDispose);
+ }
+ int key = value.JniIdentityHashCode;
+ lock (RegisteredInstances) {
+ List? peers;
+ if (!RegisteredInstances.TryGetValue (key, out peers)) {
+ peers = new List () {
+ CreateReferenceTrackingHandle (value)
+ };
+ RegisteredInstances.Add (key, peers);
+ return;
+ }
+
+ for (int i = peers.Count - 1; i >= 0; i--) {
+ var p = peers [i];
+ if (p.Target is not IJavaPeerable peer)
+ continue;
+ if (!JniEnvironment.Types.IsSameObject (peer.PeerReference, value.PeerReference))
+ continue;
+ if (Replaceable (p)) {
+ peers [i] = CreateReferenceTrackingHandle (value);
+ } else {
+ WarnNotReplacing (key, value, peer);
+ }
+ return;
+ }
+ peers.Add (CreateReferenceTrackingHandle (value));
+ }
+ }
+
+ static bool Replaceable (GCHandle handle)
+ {
+ if (handle.Target is not IJavaPeerable peer)
+ return true;
+ return peer.JniManagedPeerState.HasFlag (JniManagedPeerStates.Replaceable);
+ }
+
+ void WarnNotReplacing (int key, IJavaPeerable ignoreValue, IJavaPeerable keepValue)
+ {
+ Runtime.ObjectReferenceManager.WriteGlobalReferenceLine (
+ "Warning: Not registering PeerReference={0} IdentityHashCode=0x{1} Instance={2} Instance.Type={3} Java.Type={4}; " +
+ "keeping previously registered PeerReference={5} Instance={6} Instance.Type={7} Java.Type={8}.",
+ ignoreValue.PeerReference.ToString (),
+ key.ToString ("x", CultureInfo.InvariantCulture),
+ RuntimeHelpers.GetHashCode (ignoreValue).ToString ("x", CultureInfo.InvariantCulture),
+ ignoreValue.GetType ().FullName,
+ JniEnvironment.Types.GetJniTypeNameFromInstance (ignoreValue.PeerReference),
+ keepValue.PeerReference.ToString (),
+ RuntimeHelpers.GetHashCode (keepValue).ToString ("x", CultureInfo.InvariantCulture),
+ keepValue.GetType ().FullName,
+ JniEnvironment.Types.GetJniTypeNameFromInstance (keepValue.PeerReference));
+ }
+
+ public override IJavaPeerable? PeekPeer (JniObjectReference reference)
+ {
+ if (RegisteredInstances == null)
+ throw new ObjectDisposedException (nameof (ManagedValueManager));
+
+ if (!reference.IsValid)
+ return null;
+
+ int key = GetJniIdentityHashCode (reference);
+
+ lock (RegisteredInstances) {
+ List? peers;
+ if (!RegisteredInstances.TryGetValue (key, out peers))
+ return null;
+
+ for (int i = peers.Count - 1; i >= 0; i--) {
+ var p = peers [i];
+ if (p.Target is IJavaPeerable peer && JniEnvironment.Types.IsSameObject (reference, peer.PeerReference))
+ return peer;
+ }
+ if (peers.Count == 0)
+ RegisteredInstances.Remove (key);
+ }
+ return null;
+ }
+
+ public override void RemovePeer (IJavaPeerable value)
+ {
+ if (RegisteredInstances == null)
+ throw new ObjectDisposedException (nameof (ManagedValueManager));
+
+ if (value == null)
+ throw new ArgumentNullException (nameof (value));
+
+ int key = value.JniIdentityHashCode;
+ lock (RegisteredInstances) {
+ List? peers;
+ if (!RegisteredInstances.TryGetValue (key, out peers))
+ return;
+
+ for (int i = peers.Count - 1; i >= 0; i--) {
+ var p = peers [i];
+ if (object.ReferenceEquals (value, p.Target)) {
+ peers.RemoveAt (i);
+ FreeHandle (p);
+ }
+ }
+ if (peers.Count == 0)
+ RegisteredInstances.Remove (key);
+ }
+ }
+
+ public override void FinalizePeer (IJavaPeerable value)
+ {
+ var h = value.PeerReference;
+ var o = Runtime.ObjectReferenceManager;
+ // MUST NOT use SafeHandle.ReferenceType: local refs are tied to a JniEnvironment
+ // and the JniEnvironment's corresponding thread; it's a thread-local value.
+ // Accessing SafeHandle.ReferenceType won't kill anything (so far...), but
+ // instead it always returns JniReferenceType.Invalid.
+ if (!h.IsValid || h.Type == JniObjectReferenceType.Local) {
+ if (o.LogGlobalReferenceMessages) {
+ o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}",
+ h.ToString (),
+ value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture),
+ RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture),
+ value.GetType ().ToString ());
+ }
+ RemovePeer (value);
+ value.SetPeerReference (new JniObjectReference ());
+ value.Finalized ();
+ return;
+ }
+
+ RemovePeer (value);
+ if (o.LogGlobalReferenceMessages) {
+ o.WriteGlobalReferenceLine ("Finalizing PeerReference={0} IdentityHashCode=0x{1} Instance=0x{2} Instance.Type={3}",
+ h.ToString (),
+ value.JniIdentityHashCode.ToString ("x", CultureInfo.InvariantCulture),
+ RuntimeHelpers.GetHashCode (value).ToString ("x", CultureInfo.InvariantCulture),
+ value.GetType ().ToString ());
+ }
+ value.SetPeerReference (new JniObjectReference ());
+ JniObjectReference.Dispose (ref h);
+ value.Finalized ();
+ }
+
+ public override void ActivatePeer (IJavaPeerable? self, JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues)
+ {
+ try {
+ ActivateViaReflection (reference, cinfo, argumentValues);
+ } catch (Exception e) {
+ var m = string.Format (
+ CultureInfo.InvariantCulture,
+ "Could not activate {{ PeerReference={0} IdentityHashCode=0x{1} Java.Type={2} }} for managed type '{3}'.",
+ reference,
+ GetJniIdentityHashCode (reference).ToString ("x", CultureInfo.InvariantCulture),
+ JniEnvironment.Types.GetJniTypeNameFromInstance (reference),
+ cinfo.DeclaringType?.FullName);
+ Debug.WriteLine (m);
+
+ throw new NotSupportedException (m, e);
+ }
+ }
+
+ void ActivateViaReflection (JniObjectReference reference, ConstructorInfo cinfo, object?[]? argumentValues)
+ {
+ var declType = GetDeclaringType (cinfo);
+
+ #pragma warning disable IL2072
+ var self = (IJavaPeerable) System.Runtime.CompilerServices.RuntimeHelpers.GetUninitializedObject (declType);
+ #pragma warning restore IL2072
+ self.SetPeerReference (reference);
+
+ cinfo.Invoke (self, argumentValues);
+
+ [UnconditionalSuppressMessage ("Trimming", "IL2073", Justification = "🤷♂️")]
+ [return: DynamicallyAccessedMembers (Constructors)]
+ Type GetDeclaringType (ConstructorInfo cinfo) =>
+ cinfo.DeclaringType ?? throw new NotSupportedException ("Do not know the type to create!");
+ }
+
+ public override List GetSurfacedPeers ()
+ {
+ if (RegisteredInstances == null)
+ throw new ObjectDisposedException (nameof (ManagedValueManager));
+
+ lock (RegisteredInstances) {
+ var peers = new List (RegisteredInstances.Count);
+ foreach (var e in RegisteredInstances) {
+ foreach (var p in e.Value) {
+ if (p.Target is not IJavaPeerable peer)
+ continue;
+ peers.Add (new JniSurfacedPeerInfo (e.Key, new WeakReference (peer)));
+ }
+ }
+ return peers;
+ }
+ }
+
+ static GCHandle CreateReferenceTrackingHandle (IJavaPeerable value) =>
+ JavaMarshal.CreateReferenceTrackingHandle (value, value.JniObjectReferenceControlBlock);
+
+ static unsafe void FreeHandle (GCHandle handle)
+ {
+ IntPtr context = JavaMarshal.GetContext (handle);
+ NativeMemory.Free ((void*) context);
+ }
+
+ [UnmanagedCallersOnly]
+ internal static unsafe void MarkCrossReferences (MarkCrossReferences* crossReferences)
+ {
+ // Java.Lang.JavaSystem.Gc ();
+
+ JavaMarshal.ReleaseMarkCrossReferenceResources (crossReferences);
+ }
+ }
+}
diff --git a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs
index 4cae1dc48..722e65365 100644
--- a/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs
+++ b/src/Java.Runtime.Environment/Java.Interop/MonoRuntimeValueManager.cs
@@ -14,7 +14,7 @@ enum GCBridgeUseWeakReferenceKind {
Jni,
}
- class MonoRuntimeValueManager : JniRuntime.JniValueManager {
+ partial class MonoRuntimeValueManager : JniRuntime.JniValueManager {
#pragma warning disable 0649
// This field is mutated by the java-interop native lib
@@ -46,6 +46,10 @@ public override void OnSetRuntime (JniRuntime runtime)
throw new NotSupportedException ("Could not register current AppDomain!");
if (JreNativeMethods.java_interop_gc_bridge_set_current_once (bridge) < 0)
throw new NotSupportedException ("Could not set GC Bridge instance!");
+ unsafe {
+ if (JreNativeMethods.java_interop_gc_bridge_set_mark_cross_references(bridge, &MarkCrossReferences) < 0)
+ throw new NotSupportedException("Could not set MarkCrossReferences!");
+ }
}
catch (Exception) {
JreNativeMethods.java_interop_gc_bridge_free (bridge);
@@ -415,6 +419,18 @@ partial class JreNativeMethods {
[DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)]
internal static extern void java_interop_gc_bridge_wait_for_bridge_processing (IntPtr bridge);
+
+ [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)]
+ internal static extern unsafe int java_interop_gc_bridge_set_mark_cross_references (
+ IntPtr bridge,
+ delegate* unmanaged[Cdecl] markCrossReferences
+ );
+
+ [DllImport (JavaInteropLib, CallingConvention=CallingConvention.Cdecl)]
+ internal static extern unsafe int java_interop_gc_bridge_release_mark_cross_references_resources (
+ IntPtr bridge,
+ System.Runtime.InteropServices.Java.MarkCrossReferences* crossReferences
+ );
}
sealed class OverrideStackTrace : Exception {
diff --git a/src/java-interop/java-interop-gc-bridge-mono.cc b/src/java-interop/java-interop-gc-bridge-mono.cc
index aca306c60..759a5656d 100644
--- a/src/java-interop/java-interop-gc-bridge-mono.cc
+++ b/src/java-interop/java-interop-gc-bridge-mono.cc
@@ -30,12 +30,16 @@ using namespace xamarin::android;
typedef struct MonoJavaGCBridgeInfo {
MonoClass *klass;
- MonoClassField *handle;
- MonoClassField *handle_type;
- MonoClassField *refs_added;
- MonoClassField *weak_handle;
+ MonoClassField *jniObjectReferenceControlBlock;
} MonoJavaGCBridgeInfo;
+typedef struct JniObjectReferenceControlBlock {
+ jobject handle;
+ int handle_type;
+ jobject weak_handle;
+ int refs_added;
+} JniObjectReferenceControlBlock;
+
#define NUM_GC_BRIDGE_TYPES (4)
struct JavaInteropGCBridge {
@@ -72,6 +76,8 @@ struct JavaInteropGCBridge {
char *gref_path, *lref_path;
int gref_log_level, lref_log_level;
int gref_cleanup, lref_cleanup;
+
+ JavaInteropMarkCrossReferencesCallback mark_cross_references;
};
static jobject
@@ -366,13 +372,10 @@ java_interop_gc_bridge_register_bridgeable_type (
MonoJavaGCBridgeInfo *info = &bridge->mono_java_gc_bridge_info [i];
info->klass = mono_class_from_mono_type (type);
- info->handle = mono_class_get_field_from_name (info->klass, const_cast ("handle"));
- info->handle_type = mono_class_get_field_from_name (info->klass, const_cast ("handle_type"));
- info->refs_added = mono_class_get_field_from_name (info->klass, const_cast ("refs_added"));
- info->weak_handle = mono_class_get_field_from_name (info->klass, const_cast ("weak_handle"));
- if (info->klass == NULL || info->handle == NULL || info->handle_type == NULL ||
- info->refs_added == NULL || info->weak_handle == NULL)
+ info->jniObjectReferenceControlBlock = mono_class_get_field_from_name (info->klass, const_cast ("jniObjectReferenceControlBlock"));
+
+ if (info->klass == NULL || info->jniObjectReferenceControlBlock == NULL)
return -1;
bridge->num_bridge_types++;
return 0;
@@ -780,6 +783,18 @@ get_gc_bridge_info_for_object (JavaInteropGCBridge *bridge, MonoObject *object)
return get_gc_bridge_info_for_class (bridge, mono_object_get_class (object));
}
+static JniObjectReferenceControlBlock*
+get_gc_control_block_for_object (JavaInteropGCBridge *bridge, MonoObject *obj)
+{
+ MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (bridge, obj);
+ if (bridge_info == NULL)
+ return NULL;
+
+ JniObjectReferenceControlBlock *control_block;
+ mono_field_get_value (obj, bridge_info->jniObjectReferenceControlBlock, &control_block);
+ return control_block;
+}
+
typedef mono_bool (*MonodroidGCTakeRefFunc) (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj, const char *thread_name, int64_t thread_id);
static MonodroidGCTakeRefFunc take_global_ref;
@@ -788,12 +803,11 @@ static MonodroidGCTakeRefFunc take_weak_global_ref;
static mono_bool
take_global_ref_java (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj, const char *thread_name, int64_t thread_id)
{
- MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (bridge, obj);
- if (bridge_info == NULL)
+ JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (bridge, obj);
+ if (control_block == NULL)
return 0;
- jobject weak;
- mono_field_get_value (obj, bridge_info->weak_handle, &weak);
+ jobject weak = control_block->weak_handle;
jobject handle = env->CallObjectMethod (weak, bridge->WeakReference_get);
log_gref (bridge, "*try_take_global_2_1 obj=%p -> wref=%p handle=%p\n", obj, weak, handle);
@@ -808,12 +822,10 @@ take_global_ref_java (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj,
java_interop_gc_bridge_weak_gref_log_delete (bridge, weak, get_object_ref_type (env, weak), thread_name, thread_id, "take_global_ref_java");
env->DeleteGlobalRef (weak);
weak = NULL;
- mono_field_set_value (obj, bridge_info->weak_handle, &weak);
-
- mono_field_set_value (obj, bridge_info->handle, &handle);
- int type = JNIGlobalRefType;
- mono_field_set_value (obj, bridge_info->handle_type, &type);
+ control_block->weak_handle = weak;
+ control_block->handle = handle;
+ control_block->handle_type = JNIGlobalRefType;
return handle != NULL;
}
@@ -821,12 +833,11 @@ take_global_ref_java (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj,
static mono_bool
take_weak_global_ref_java (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj, const char *thread_name, int64_t thread_id)
{
- MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (bridge, obj);
- if (bridge_info == NULL)
+ JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (bridge, obj);
+ if (control_block == NULL)
return 0;
- jobject handle;
- mono_field_get_value (obj, bridge_info->handle, &handle);
+ jobject handle = control_block->handle;
jobject weaklocal = env->NewObject (bridge->WeakReference_class, bridge->WeakReference_init, handle);
jobject weakglobal = env->NewGlobalRef (weaklocal);
@@ -838,8 +849,8 @@ take_weak_global_ref_java (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject
java_interop_gc_bridge_gref_log_delete (bridge, handle, get_object_ref_type (env, handle), thread_name, thread_id, "take_weak_global_ref_2_1_compat");
env->DeleteGlobalRef (handle);
-
- mono_field_set_value (obj, bridge_info->weak_handle, &weakglobal);
+ control_block->handle = NULL;
+ control_block->weak_handle = weakglobal;
return 1;
}
@@ -847,13 +858,11 @@ take_weak_global_ref_java (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject
static mono_bool
take_global_ref_jni (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj, const char *thread_name, int64_t thread_id)
{
- MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (bridge, obj);
- if (bridge_info == NULL)
+ JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (bridge, obj);
+ if (control_block == NULL)
return 0;
- jobject weak;
- mono_field_get_value (obj, bridge_info->handle, &weak);
-
+ jobject weak = control_block->handle;
jobject handle = env->NewGlobalRef (weak);
log_gref (bridge, "*try_take_global obj=%p -> wref=%p handle=%p\n", obj, weak, handle);
@@ -868,22 +877,20 @@ take_global_ref_jni (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj,
thread_name, thread_id, "take_global_ref_jni");
env->DeleteWeakGlobalRef (weak);
- mono_field_set_value (obj, bridge_info->handle, &handle);
+ control_block->handle = handle;
+ control_block->handle_type = JNIGlobalRefType;
- int type = JNIGlobalRefType;
- mono_field_set_value (obj, bridge_info->handle_type, &type);
return handle != NULL;
}
static mono_bool
take_weak_global_ref_jni (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj, const char *thread_name, int64_t thread_id)
{
- MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (bridge, obj);
- if (bridge_info == NULL)
+ JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (bridge, obj);
+ if (control_block == NULL)
return 0;
- jobject handle;
- mono_field_get_value (obj, bridge_info->handle, &handle);
+ jobject handle = control_block->handle;
log_gref (bridge, "*take_weak obj=%p; handle=%p\n", obj, handle);
@@ -896,10 +903,8 @@ take_weak_global_ref_jni (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *
thread_name, thread_id, "take_weak_global_ref_jni");
env->DeleteGlobalRef (handle);
- mono_field_set_value (obj, bridge_info->handle, &weak);
-
- int type = JNIWeakGlobalRefType;
- mono_field_set_value (obj, bridge_info->handle_type, &type);
+ control_block->handle = weak;
+ control_block->handle_type = JNIWeakGlobalRefType;
return 1;
}
@@ -921,17 +926,18 @@ get_add_reference_method (JavaInteropGCBridge *bridge, JNIEnv *env, jobject obj,
}
static mono_bool
-add_reference (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj, MonoJavaGCBridgeInfo *bridge_info, MonoObject *reffed_obj)
+add_reference (JavaInteropGCBridge *bridge, JNIEnv *env, MonoObject *obj, JniObjectReferenceControlBlock *control_block, MonoObject *reffed_obj)
{
MonoClass *klass = mono_object_get_class (obj);
- jobject handle;
- mono_field_get_value (obj, bridge_info->handle, &handle);
+ jobject handle = control_block->handle;
jmethodID add_method_id = get_add_reference_method (bridge, env, handle, klass);
if (add_method_id) {
- jobject reffed_handle;
- mono_field_get_value (reffed_obj, bridge_info->handle, &reffed_handle);
+ JniObjectReferenceControlBlock *reffed_control_block = get_gc_control_block_for_object (bridge, reffed_obj);
+ if (reffed_control_block == NULL)
+ return 0;
+ jobject reffed_handle = reffed_control_block->handle;
env->CallVoidMethod (handle, add_method_id, reffed_handle);
#if DEBUG
if (bridge->gref_log_level > 1)
@@ -979,28 +985,30 @@ gc_prepare_for_java_collection (JavaInteropGCBridge *bridge, JNIEnv *env, int nu
/* add java refs for items on the list which reference each other */
for (int i = 0; i < num_sccs; i++) {
MonoGCBridgeSCC *scc = sccs [i];
- MonoJavaGCBridgeInfo *bridge_info = NULL;
+
+ JniObjectReferenceControlBlock *control_block = NULL;
+
/* start at the second item, ref j from j-1 */
for (int j = 1; j < scc->num_objs; j++) {
- bridge_info = get_gc_bridge_info_for_object (bridge, scc->objs [j-1]);
- if (bridge_info != NULL && add_reference (bridge, env, scc->objs [j-1], bridge_info, scc->objs [j])) {
- mono_field_set_value (scc->objs [j-1], bridge_info->refs_added, &ref_val);
+ control_block = get_gc_control_block_for_object (bridge, scc->objs [j-1]);
+ if (control_block != NULL && add_reference (bridge, env, scc->objs [j-1], control_block, scc->objs [j])) {
+ control_block->refs_added = ref_val;
}
}
/* ref the first from the last */
if (scc->num_objs > 1) {
- bridge_info = get_gc_bridge_info_for_object (bridge, scc->objs [scc->num_objs-1]);
- if (bridge_info != NULL && add_reference (bridge, env, scc->objs [scc->num_objs-1], bridge_info, scc->objs [0])) {
- mono_field_set_value (scc->objs [scc->num_objs-1], bridge_info->refs_added, &ref_val);
+ control_block = get_gc_control_block_for_object (bridge, scc->objs [scc->num_objs-1]);
+ if (control_block != NULL && add_reference (bridge, env, scc->objs [scc->num_objs-1], control_block, scc->objs [0])) {
+ control_block->refs_added = ref_val;
}
}
}
/* add the cross scc refs */
for (int i = 0; i < num_xrefs; i++) {
- MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (bridge, sccs [xrefs [i].src_scc_index]->objs [0]);
- if (bridge_info != NULL && add_reference (bridge, env, sccs [xrefs [i].src_scc_index]->objs [0], bridge_info, sccs [xrefs [i].dst_scc_index]->objs [0])) {
- mono_field_set_value (sccs [xrefs [i].src_scc_index]->objs [0], bridge_info->refs_added, &ref_val);
+ JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (bridge, sccs [xrefs [i].src_scc_index]->objs [0]);
+ if (control_block != NULL && add_reference (bridge, env, sccs [xrefs [i].src_scc_index]->objs [0], control_block, sccs [xrefs [i].dst_scc_index]->objs [0])) {
+ control_block->refs_added = ref_val;
}
}
@@ -1041,19 +1049,18 @@ gc_cleanup_after_java_collection (JavaInteropGCBridge *bridge, JNIEnv *env, int
sccs [i]->is_alive = 0;
for (int j = 0; j < sccs [i]->num_objs; j++) {
MonoObject *obj = sccs [i]->objs [j];
- MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (bridge, obj);
- if (bridge_info == NULL)
+
+ JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (bridge, obj);
+ if (control_block == NULL)
continue;
- jobject jref;
- mono_field_get_value (obj, bridge_info->handle, &jref);
+ jobject jref = control_block->handle;
if (jref) {
alive++;
if (j > 0)
assert (sccs [i]->is_alive);
sccs [i]->is_alive = 1;
- int refs_added;
- mono_field_get_value (obj, bridge_info->refs_added, &refs_added);
+ int refs_added = control_block->refs_added;
if (refs_added) {
jmethodID clear_method_id = get_clear_references_method (bridge, env, jref);
if (clear_method_id) {
@@ -1201,13 +1208,12 @@ gc_bridge_class_kind (MonoClass *klass)
static mono_bool
gc_is_bridge_object (MonoObject *object)
{
- void *handle;
- MonoJavaGCBridgeInfo *bridge_info = get_gc_bridge_info_for_object (mono_bridge, object);
- if (bridge_info == NULL)
- return 0;
+ JniObjectReferenceControlBlock *control_block = get_gc_control_block_for_object (mono_bridge, object);
+ if (control_block == NULL)
+ return 0;;
- mono_field_get_value (object, bridge_info->handle, &handle);
+ void *handle = control_block->handle;
if (handle == NULL) {
#if DEBUG
MonoClass *mclass = mono_object_get_class (object);
@@ -1279,6 +1285,49 @@ gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGC
free (thread_name);
}
+static void
+managed_gc_cross_references (int num_sccs, MonoGCBridgeSCC **sccs, int num_xrefs, MonoGCBridgeXRef *xrefs)
+{
+ fprintf (stderr, "# jonp: managed_gc_cross_references: %d sccs and %d xrefs\n", num_sccs, num_xrefs);
+ if (mono_bridge->mark_cross_references == NULL) {
+ assert (!"mono_bridge->mark_cross_references is NULL; WE SHOULD NOT BE EXECUTING");
+ return;
+ }
+ int i;
+
+ Srij_MarkCrossReferences cross_references = {};
+
+ cross_references.ComponentsLen = (void*) (intptr_t) num_sccs;
+ cross_references.Components = (Srij_StronglyConnectedComponent*) calloc (num_sccs, sizeof (Srij_StronglyConnectedComponent));
+ for (i = 0; i < num_sccs; ++i) {
+ Srij_StronglyConnectedComponent *scc = &cross_references.Components [i];
+
+ scc->Count = (void*) (intptr_t) sccs [i]->num_objs;
+ scc->Context = (void**) calloc (sccs [i]->num_objs, sizeof (void*));
+ for (int j = 0; j < sccs [i]->num_objs; ++j) {
+ MonoObject *obj = sccs [i]->objs [j];
+ scc->Context [j] = get_gc_control_block_for_object (mono_bridge, obj);
+ }
+ }
+
+ cross_references.CrossReferencesLen = (void*) (intptr_t) num_xrefs;
+ cross_references.CrossReferences = (Srij_ComponentCrossReference*) calloc (num_xrefs, sizeof (Srij_ComponentCrossReference));
+ for (i = 0; i < num_xrefs; ++i) {
+ Srij_ComponentCrossReference *xref = &cross_references.CrossReferences [i];
+ xref->SourceGroupIndex = (void*) (intptr_t) xrefs [i].src_scc_index;
+ xref->DestinationGroupIndex = (void*) (intptr_t) xrefs [i].dst_scc_index;
+ }
+
+ fprintf (stderr, "# jonp: calling managed mark_cross_references\n");
+ mono_bridge->mark_cross_references (&cross_references);
+
+ for (i = 0; i < num_sccs; ++i) {
+ Srij_StronglyConnectedComponent *scc = &cross_references.Components [i];
+ sccs [i]->is_alive = scc->IsAlive;
+ }
+ fprintf (stderr, "# jonp: calling done\n");
+}
+
int
java_interop_gc_bridge_register_hooks (JavaInteropGCBridge *bridge, int weak_ref_kind)
{
@@ -1312,7 +1361,9 @@ java_interop_gc_bridge_register_hooks (JavaInteropGCBridge *bridge, int weak_ref
bridge_cbs.bridge_version = SGEN_BRIDGE_VERSION;
bridge_cbs.bridge_class_kind = gc_bridge_class_kind;
bridge_cbs.is_bridge_object = gc_is_bridge_object;
- bridge_cbs.cross_references = gc_cross_references;
+ bridge_cbs.cross_references = bridge->mark_cross_references
+ ? managed_gc_cross_references
+ : gc_cross_references;
mono_gc_register_bridge_callbacks (&bridge_cbs);
@@ -1328,3 +1379,28 @@ java_interop_gc_bridge_wait_for_bridge_processing (JavaInteropGCBridge *bridge)
mono_gc_wait_for_bridge_processing ();
return 0;
}
+
+int
+java_interop_gc_bridge_set_mark_cross_references (JavaInteropGCBridge *bridge, JavaInteropMarkCrossReferencesCallback markCrossReferences)
+{
+ if (bridge == NULL)
+ return -1;
+
+ bridge->mark_cross_references = markCrossReferences;
+
+ return 0;
+}
+
+int
+java_interop_gc_bridge_release_mark_cross_references_resources (JavaInteropGCBridge *bridge, Srij_MarkCrossReferences *crossReferences)
+{
+ if (bridge == NULL)
+ return -1;
+
+ if (crossReferences == NULL)
+ return -1;
+
+ // leak it…
+
+ return 0;
+}
diff --git a/src/java-interop/java-interop-gc-bridge.h b/src/java-interop/java-interop-gc-bridge.h
index 8c8444cb6..6715b33be 100644
--- a/src/java-interop/java-interop-gc-bridge.h
+++ b/src/java-interop/java-interop-gc-bridge.h
@@ -19,6 +19,26 @@ struct JavaInterop_System_RuntimeTypeHandle {
void *value;
};
+typedef struct Srij_ComponentCrossReference {
+ void *SourceGroupIndex;
+ void *DestinationGroupIndex;
+} Srij_ComponentCrossReference;
+
+typedef struct Srij_StronglyConnectedComponent {
+ int IsAlive;
+ void *Count;
+ void **Context;
+} Srij_StronglyConnectedComponent;
+
+typedef struct Srij_MarkCrossReferences {
+ void *ComponentsLen;
+ Srij_StronglyConnectedComponent *Components;
+ void* CrossReferencesLen;
+ Srij_ComponentCrossReference *CrossReferences;
+} Srij_MarkCrossReferences;
+
+typedef void (*JavaInteropMarkCrossReferencesCallback) (Srij_MarkCrossReferences *crossReferences);
+
JAVA_INTEROP_API JavaInteropGCBridge *java_interop_gc_bridge_get_current (void);
JAVA_INTEROP_API int java_interop_gc_bridge_set_current_once (JavaInteropGCBridge *bridge);
@@ -32,6 +52,10 @@ JAVA_INTEROP_API int java_interop_gc_bridge_add_current_a
JAVA_INTEROP_API int java_interop_gc_bridge_remove_current_app_domain (JavaInteropGCBridge *bridge);
JAVA_INTEROP_API int java_interop_gc_bridge_set_bridge_processing_field (JavaInteropGCBridge *bridge, struct JavaInterop_System_RuntimeTypeHandle type_handle, char *field_name);
+
+JAVA_INTEROP_API int java_interop_gc_bridge_set_mark_cross_references (JavaInteropGCBridge *bridge, JavaInteropMarkCrossReferencesCallback markCrossReferences);
+JAVA_INTEROP_API int java_interop_gc_bridge_release_mark_cross_references_resources (JavaInteropGCBridge *bridge, Srij_MarkCrossReferences *crossReferences);
+
JAVA_INTEROP_API int java_interop_gc_bridge_register_bridgeable_type (JavaInteropGCBridge *bridge, struct JavaInterop_System_RuntimeTypeHandle type_handle);
JAVA_INTEROP_API int java_interop_gc_bridge_enable (JavaInteropGCBridge *bridge, int enable);
diff --git a/tests/Java.Interop-Tests/Java.Interop/JavaObjectArrayTest.cs b/tests/Java.Interop-Tests/Java.Interop/JavaObjectArrayTest.cs
index f02bbc06b..896c1c9a6 100644
--- a/tests/Java.Interop-Tests/Java.Interop/JavaObjectArrayTest.cs
+++ b/tests/Java.Interop-Tests/Java.Interop/JavaObjectArrayTest.cs
@@ -39,6 +39,30 @@ protected override bool SequenceEqual (IEnumerable a, IEnumerable b)
{
return JniMarshal.RecursiveEquals (a, b);
}
+
+ int beforeTestGrefCount;
+
+ [SetUp]
+ public void LogBeginCurrentTestGrefCount ()
+ {
+ beforeTestGrefCount = JniEnvironment.Runtime.GlobalReferenceCount;
+ JniEnvironment.Runtime.ObjectReferenceManager.WriteGlobalReferenceLine(
+ "{0}",
+ $"Begin {TestContext.CurrentContext.Test.FullName}; " +
+ $"GREFs={beforeTestGrefCount}; "
+ );
+ }
+
+ [TearDown]
+ public void LogEndCurrentTestToGrefCount ()
+ {
+ int afterTestGrefCount = JniEnvironment.Runtime.GlobalReferenceCount;
+ JniEnvironment.Runtime.ObjectReferenceManager.WriteGlobalReferenceLine(
+ "{0}",
+ $"End {TestContext.CurrentContext.Test.FullName}; " +
+ $"GREFs={afterTestGrefCount}; diff={afterTestGrefCount - beforeTestGrefCount}"
+ );
+ }
}
[TestFixture]