On Jun 17, 2016, at 9:50 AM, Jeremy A. Kolb - ARA/NED <jk...@ara.com> wrote:
> I'm investigating https://github.com/MvvmCross/MvvmCross/issues/1343 for a 
> possible memory leak related to our version of the RecyclerView widget 
> MvxRecyclerView and have run against an interesting issue.
> 
> When swapping out the itemsource in the adapter the RecyclerView instantiates 
> ViewHolders but never seems to kill them no matter how many times I force a 
> GC.  If I add a "~MvxRecyclerViewHolder()"  method to MvxRecyclerViewHolder I 
> never get in there.  However I do hit the JavaFinalize method and now I am 
> completely confused.
> 
> Why would I hit the JavaFinalizer and not the .NET one?  There is never an 
> explicit Dispose on the MvxRecyclerView so I can't see why the Java -> .NET 
> connection would be severed.
> 
> The only instance I've found where Xamarin uses JavaFinalize is at 
> https://github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Platform.Android/Renderers/GenericAnimatorListener.cs#L35
>  which doesn't explain much.
> 
> Am I seeing a bug in Xamarin?  In MvvmCross's RecyclerView?  Or more likely: 
> am I just confused about the interaction of two garbage collectors.

The interaction is deep voodoo. :-)

As usual with these things, enable GREF logging:

        
https://developer.xamarin.com/guides/android/troubleshooting/troubleshooting/#Global_Reference_Messages
        
https://developer.xamarin.com/releases/android/xamarin.android_5/xamarin.android_5.1/#GREF_Logging_Changes

        $ adb shell setprop debug.mono.log gref,gc

The relevant GC bridge code is `take_global_ref_jni()`, 
`take_weak_global_ref_jni()`, and `gc_cross_references()`:

        
https://github.com/xamarin/xamarin-android/blob/c3811e8/src/monodroid/jni/monodroid-glue.c#L1140-L1202
        
https://github.com/xamarin/xamarin-android/blob/c3811e8/src/monodroid/jni/monodroid-glue.c#L1483-L1522

This is run as part of Mono’s GC bridge code…which I’m kinda flaky on. The 
*general* idea is as follows:

1. Mono’s GC looks for finalizable objects.
2. When a finalizable instance has no more managed references, it’s placed into 
some finalizer queue
3. *Before* the finalizer queue is emptied, the GC bridge is queried to 
determine if the instance is *actually* finalizable.
3.a. If the instance can be finalized, it will be during the next GC.
3.b. If the instance can’t be finalized, it will be kept alive.

The GC bridge, in this context, is `gc_cross_references()`, which is given a 
set of Java.Lang.Object instances, and:

1. Toggles all the GREFs to Weak GREFs…for the *entire object graph* the 
instance refers to. (Plus some mirroring of the object graph in Java-land…)
2. Kicks off a Java-side GC.
3. Checks to see if anything was collected. If it was collected, Mono’s GC is 
told that the instance can be finalized. Otherwise, the instance is kept alive.
4. For all alive instances, toggle the Weak GREFs to GREFs.

Note: mid-term plan is to unify that code with the ~equivalent code here:

        
https://github.com/xamarin/java.interop/blob/master/src/java-interop/java-interop-gc-bridge-mono.c

That project keeps getting punted as more important work pops up…

Very important question: what Android version do you see this on? There are two 
Weak reference implementations, one that uses “proper” JNI Weak Global 
References, and one which uses java.lang.WeakReference (because API-8 was the 
first to support JNI Weak Global references, and then Android 4.4 + ART broke 
them…). The  WeakReference implementation doesn’t get as much testing, so…it’s 
possibly suspect unless you can discount it.

Returning to the original description…
`Java.Lang.Object.Handle` is a JNI Global Reference, which *should* keep the 
instance alive. Period. Thus, it shouldn’t be possible for 
`Object.JavaFinalize()` to be invoked while `Object.Handle` is not IntPtr.Zero.

I can think of *one* way it would happen, but `GenericAnimatorListener` doesn’t 
trigger it: if the class had a `T(IntPtr, JniHandleOwnership)` constructor, 
*and* the instance were `Dispose()`d, then I can see `JavaFinalize()` being 
invoked:

        class Silly : Java.Lang.Object {
                public Silly () {}
                public Silly (IntPtr h, JniHandleOwnership t) : base (h, t) {}
                protected override void JavaFinalize () {
                }
        }
        // …
        using (var s = new Silly ()) {
        }

Once `s.Dispose()` is invoked, nothing will keep the Java-side peer alive, so 
later `Object.finalize()` may eventually be invoked by the Java GC, but since 
there is no longer a registered peer for the JNI instance, we’ll invoke the 
`Silly(IntPtr, JniHandleOwnership)` constructor to *create a new* peer, and 
invoke `Silly.JavaFinalize()` on the newly created peer, *not* the original 
instance:

        // above `using` block + subsequent behavior is as if…
        new Silly ().Dispose();
        // time passes...
        new Silly (value, JniHandleOwnership.DoNotTransfer).JavaFinalize();

That’s clearly not the case in GenericAnimatorListener — it doesn’t have the 
right constructor.

That *may* be the case in MvxRecyclerViewHolder, as it *does* have the 
activation constructor:

        
https://github.com/MvvmCross/MvvmCross-AndroidSupport/blob/e88c556/MvvmCross.Droid.Support.V7.RecyclerView/MvxRecyclerViewHolder.cs#L107-L109


        public MvxRecyclerViewHolder(IntPtr handle, JniHandleOwnership transfer)
            : base(handle, transfer)
        {}

I would thus suggest you look into *why* `MvxRecyclerViewHolder ` has an 
Activation constructor. I strongly suggest *avoiding* activation constructors 
unless absolutely necessary:

        
https://developer.xamarin.com/guides/android/under_the_hood/architecture/#Java_Activation

The Activation constructor has a tendency to “hide” or “change” the behavior of 
actual buggy behavior, so the fact that `MvxRecyclerViewHolder` provides an 
activation constructor without overriding any virtual methods (other than 
Dispose()) does not fill me with confidence.

If this is happening, you’d see the `MvxRecyclerViewHolder._bindingContext` is 
null — along with all over members! You could check for that within your 
`JavaFinalize()` override, and see if the instance has “meaningful” data 
compared to what the instance had after construction.

Additionally, to track *managed* instance lifetimes, I suggest copious use of 
RuntimeHelpers.GetHashCode(object):

        https://msdn.microsoft.com/en-us/library/11tbk3h9(v=vs.110).aspx

 - Jon

_______________________________________________
Monodroid mailing list
Monodroid@lists.ximian.com

UNSUBSCRIBE INFORMATION:
http://lists.ximian.com/mailman/listinfo/monodroid

Reply via email to