3
\$\begingroup\$

At work we are using Xamarin.iOS for our apps. We frequently have to fight it's disadvantages, namely it's memory leak issues where strong cyclic references keep objects alive and prevent them from being GC'ed just because someone somewhere forgot to set a property to null before discarding a view controller. We previously used reflection to iterate through all properties and fields to set them to null however our heavy use of reflection in cleanup code lead to some performance slow downs as well as bugs where someone forgot to check for value types and tried to assign null to them. Instead of going crazy with reflection I thought about just memset-ing the whole object to 0x0 like this:

public static class ReferenceDestructor
{
    /// <summary>
    /// Sets everything to <see langword="null"/>.
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <param name="target"></param>
    public static unsafe void DestroyAllReferencesUnsafe<T>(T? target) where T : class
    {
        if (target is not null)
        {
            // don't go crazy with reflection here. We just zero the whole object.
            // pin the target object in memory.
            using PinnedHandle pinnedHandle = new(target);

            // get size of managed object
            int unsafeManagedSize = Unsafe.SizeOf<T>();

            // basically get a pointer to *managed* memory.
            void* objectAddress = (void*)pinnedHandle.GCHandle.AddrOfPinnedObject();

            // Set unsafeManagedSize bytes at objectAddress to zero, zero-ing the whole object and setting all
            // properties and fields to null or 0 or whatever.
            Unsafe.InitBlock(objectAddress, 0x0, (uint)unsafeManagedSize);
        }
    }
}

Now apart from the liberal usage of unsafe and pointers, I wonder if this is a viable solution to solve the problem? Being only a four-liner it is certainly shorter than iterating over every field and property with reflection, having to deal with readonly or init-only access modifiers.

\$\endgroup\$
3
  • 1
    \$\begingroup\$ That's a pretty neat hack... if it works. Did you try it in production ;-P Anaways, could you tell me where I can find the PinnedHandle, cannot figure this one out or is it some Xamarin thingy? \$\endgroup\$
    – t3chb0t
    Dec 31, 2021 at 21:43
  • 1
    \$\begingroup\$ I'm no Xamarin dev but I'm highly suspicious of needing to go this route and wondering if this isn't a matter of reckless memory management that should be avoided instead of worked around. I'm hard-pressed to believe that Xamarin is this incapable of letting its garbage be collected. \$\endgroup\$
    – Flater
    Feb 3, 2022 at 15:56
  • \$\begingroup\$ @Flater You raise an important point. Further, what happens if one of the objects referenced by target is an unmanaged resource? Surely this isn't a safe way of disposing of/releasing those resources. \$\endgroup\$
    – Mike Hofer
    Feb 4, 2022 at 13:41

1 Answer 1

1
\$\begingroup\$

If I understand the Garbage Collector properly, setting an object instance to null doesn't actually release its memory. That won't happen until a GC.Collect occurs, which, according to Understanding Garbage Collection in .NET on C# Corner, is triggered under these conditions:

  1. The system has low physical memory and gets notification from OS.
  2. The threshold for an object on heap surpasses; the threshold is not constant but it keeps changing as the process runs.
  3. GC.Collect is called but it is required in rare cases because Garbage Collection runs continuously and in parallel with processes.

Essentially, either there's too much pressure on the heap, there's not enough free contiguous memory to allocate a newly requested object, or someone called GC.Collect directly (not advisable). In these situations, heap fragmentation is a fairly common culprit.

Having said all that, I'm not entirely certain what effect your solution would have on the garbage collector. Somewhere in memory, there's a reference pointing to the zeroed-out object. That reference may be referenced by other objects. As long as that reference is still in use, it won't be released by the garbage collector, even if its contents are zeroed out.

\$\endgroup\$
2
  • \$\begingroup\$ "it won't be released by the garbage collector, even if its contents are zeroed out" I think you've inverted the goal here. I suspect the goal is to set properties to null so that the objects that were being referenced in those properties are no longer referenced and can be collected. Not so the "parent" can be collected (unless all of its parents get zeroed out as well). \$\endgroup\$
    – Flater
    Feb 3, 2022 at 15:55
  • \$\begingroup\$ @Flater That is entirely possible. In retrospect, zeroing out the object would be a brute-force mechanism for releasing any references it has to objects on the heap; however, those objects would still remain on the heap until a GC ran. \$\endgroup\$
    – Mike Hofer
    Feb 3, 2022 at 16:15

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.