Patterns and best practices
While dealing with managed runtime and garbage collection, there are certain patterns and anti-patterns developers must be careful with. If not handled properly, both managed and native objects can produce noncollectable traces, which in turn can cause memory leaks and unnecessary resource consumption.
Disposable objects
The resources managed by the garbage collector are generally limited to memory allocations. Other resources like network sockets, database handles, UI elements, and file/device descriptors need to have additional definitions or mechanisms.
In managed runtime, these object resources can be cleaned up in two different ways. The first, less efficient, unpredictable way is to implement a destructor/finalizer. With a finalizer implementation, once the garbage collector decides the object is no longer strongly reachable, the resources such as network sockets can be disposed. However, finalizable objects have to wait for the following GC cycle to be cleaned up and cannot be finalized with developers' initiatives.
Another way to clean-up application resources is to implement the IDisposable
interface in the class that has the references to the resources. This interface requires only a single Dispose
method implementation to get rid of managed resources. The garbage collector also offers a method (GC.SuppressFinalize
) to avoid finalization since the object is going to be disposed using the IDisposable
interface.
public class DisposableFinalizableClass : IDisposable { private ManagedResource m_Resource; // reference to a resource public DisposableFinalizableClass() { m_Resource = new ManagedResource(); // allocates the resource } /// <summary> /// Destructor for the DisposableFinalizableClass /// <remarks> /// Note that we are not overriding the object.Finalize method /// but providing a destructor for the Finalize method to call /// </remarks> /// </summary> ~DisposableFinalizableClass() { Dispose(false); } /// <summary> /// Implementing the Dispose method /// </summary> public void Dispose() { Dispose(true); // Letting the GC know that there is no more need for // Finalization, the resources are already cleaned-up GC.SuppressFinalize(this); } protected virtual void Dispose(bool disposing) { if (disposing) { if (m_Resource != null) m_Resource.Dispose(); } else { // Called from Finalize // Clean-up any unmanaged resources } } }
The fact that disposable objects can be used together with using
blocks, gives a deterministic way for developers to release associated resources as soon as the object is no longer needed.
The lapsed listener problem
One of the most common patterns used with UI elements or legacy API implementations is the observer pattern. As you might know, there are two stakeholders in this pattern, the observer and provider. The observer subscribes to the event provided by the provider to receive updates.
The lapsed listener problem occurs when the observer pattern is implemented incorrectly or better yet incompletely. In this pattern, after the subscription, the provider keeps a strong reference to the observer. If this subscription is not removed before the subscriber goes out of context, the application will leak the subscriber object since it cannot be garbage collected (for example, an Android activity, or a view model).
In order to demonstrate this problem, we will use a singleton implementation of Fibonacci sequence with asynchronous methods as the event provider.
public event EventHandler<int> CalculationCompleted; public event EventHandler<List<int>> RangeCalculationCompleted; /// <summary> /// Calculates n-th number in Fibonacci sequence /// </summary> /// <param name="ordinal">Ordinal of the number to calculate</param> public void GetFibonacciNumberAsync(int ordinal) { var task = Task.Run(() => { var result = GetFibonacciNumberInternal(ordinal); if (CalculationCompleted != null) CalculationCompleted(this, result); }); // Avoiding Deadlocks on the UI thread task.ConfigureAwait(false); } /// <summary> /// Calculates a range of numbers in Fibonnaci sequence /// </summary> /// <param name="firstOrdinal">Ordinal of the first number</param> /// <param name="lastOrdinal">Ordinal of the last number</param> public void GetFibonacciRangeAsync(int firstOrdinal, int lastOrdinal) { var task = Task.Run(() => { var result = GetFibonacciRangeInternal(firstOrdinal, lastOrdinal); if (RangeCalculationCompleted != null) RangeCalculationCompleted(this, result); }); task.ConfigureAwait(false); } public static FibonacciSource Instance { get { return m_Instance ?? (m_Instance = new FibonacciSource()); } }
We will implement two separate view models using MvvmCross and use associated views to invoke the asynchronous methods, then navigate back to the main view using the Close
method on the view models. In the constructor of each view model, we will be subscribing to the respective event on the FibonacciSource
.
Figure 6: Fibonacci Calculator App
In order to investigate any memory leaks, we navigate back and forth between the main and the calculation views. After a couple of iterations on both of the views (that is, single and range), we have the results shown below on the Xamarin Profiler (just using the "Allocations" template.)
Figure 7: Xamarin Profiler Results
You will notice that none of the instances of SingleCalculationViewModel
are alive after garbage collection (you can trigger a GC run with GC.Collect()
), however RangeCalculationViewModel
instances are persistent. The reason for this is the missing unsubscribe call in the close command of the RangeCalculationViewModel
.
private MvxCommand m_CloseCommand; public ICommand CloseCommand { get { m_CloseCommand = m_CloseCommand ?? new MvxCommand(DoClose); return m_CloseCommand; } } private void DoClose() { // FibonacciSource.Instance.RangeCalculationCompleted -= OnCalculationCompleted; Close(this); }
We could have also used the OnPause
event on this Android application or any other relevant event on other platforms to get rid of the subscription before the subscriber or the view component that holds the subscriber goes out of context.
In this scenario, another solution would be to use a TaskCompletionSource
to convert the observable pattern to an awaitable one. Wrapping up the observable Fibonacci source would give you a better control over the subscription and the resulting asynchronous task would be better suited for mobile development and MVVM pattern.
private Task<List<int>> CalculateFibonacciRangeAsync(int firstOrdinal, int secondOrdinal) { TaskCompletionSource<List<int>> taskCompletionSource = new TaskCompletionSource<List<int>>(); EventHandler<List<int>> calcCompletedEventHandler = null; calcCompletedEventHandler = (sender, e) => { FibonacciSource.Instance.RangeCalculationCompleted -= calcCompletedEventHandler; taskCompletionSource.SetResult(e); }; FibonacciSource.Instance.RangeCalculationCompleted += calcCompletedEventHandler; FibonacciSource.Instance.GetFibonacciRangeAsync(firstOrdinal, secondOrdinal); return taskCompletionSource.Task; }
Finally, this async task would be called with a ContinueWith
statement to set the result in the view model.
private void DoCalculate() { if (!string.IsNullOrWhiteSpace(Input1) && !string.IsNullOrWhiteSpace(Input2)) { int numberOrdinal1, numberOrdinal2; if (int.TryParse(Input1, out numberOrdinal1) && int.TryParse(Input2, out numberOrdinal2)) { InfoText = "Calculating"; var fibonacciTask = CalculateFibonacciRangeAsync(numberOrdinal1, numberOrdinal2) .ContinueWith(task => { Result = string.Join(",", task.Result); InfoText = ""; }); fibonacciTask.ConfigureAwait(false); return; } } InfoText = "Invalid Input"; }
Weak references
Weak references can be of great assistance while dealing with loosely coupled application layers. In these type of scenarios, where objects need to be managed outside the class domain, weak referencing can be used to remove these instances from the GC protection based on the notion of reachability because of the strong references they have to other layers of the application.
Let us assume in the previous example that the Fibonacci sequence items are handled as reference values with a class called FibonacciItem
. This class carries the value calculated and the time it was calculated.
public class FibonacciItem { public int Value { get; private set; } private readonly DateTime m_Calculated; public FibonacciItem(int value, DateTime calculatedTime) { Value = value; m_Calculated = calculatedTime; } }
To decrease the processing time, we can now implement a caching mechanism which would force the source to recalculate the value according to the ordinal if it does not already exist in the cache or just does not sound right is disposed of in favor of memory resources. For this purpose we can use the WeakReference
to cache Fibonacci items.
public class FibonacciCache { // Dictionary to contain the cache. private static Dictionary<int, WeakReference> _cache; public FibonacciCache() { _cache = new Dictionary<int, WeakReference>(); } /// <summary> /// Accessor to FibonacciItem references /// </summary> /// <param name="ordinal"></param> /// <returns>FibonacciItem if it is still alive</returns> public FibonacciItem this[int ordinal] { get { if (!_cache.ContainsKey(ordinal)) return null; if (_cache[ordinal].IsAlive) { // Object was obtained with the weak reference. FibonacciItem cachedItem = _cache[ordinal].Target as FibonacciItem; return cachedItem; } else { // Object reference is already disposed of return null; } } set { _cache[ordinal] = new WeakReference(value); } } }
Cross-domain objects
In Xamarin applications, one of the most common memory issues, cross-heap references, occur when there is a cross-over between the native runtime and mono runtime. This issue stems from the fact that mono runtime is almost handled as a separate domain and managed in a heap only with GC handles to the native domain.
In an Android scenario, where Java objects are referenced by managed C# objects or vice versa, the communication between the two runtimes becomes expensive. For instance, if we were implementing the Fibonacci calculator without using the ViewModel pattern, we would want to create a data adaptor to load the range calculation results into a list view.
private void OnFibonacciCalculationCompleted(object sender, List<FibonacciItem> result) { RunOnUiThread(() => { var listView = FindViewById<ListView>(Resource.Id.lstResult); listView.Adapter = new ArrayAdapter<string>(this, Resource.Layout.ListViewItem, result.Select(val => val.Value.ToString()).ToArray()); }); }
This implementation has a higher cost of being garbage collected. It also has performance penalties considering the language crossing, not to mention the fact that objects from each world are effectively mirrored increasing the memory allocation costs.
The solution here would be to do as much work as possible in the managed world and let the runtime take care of the rest. So instead of using the native ArrayAdapter
, we could implement a base adapter that would feed the FibonacciItem
instances to the ListView
.
public class FibonacciResultsAdapter : BaseAdapter<FibonacciItem> { List<FibonacciItem> m_ResultItems; Activity m_Context; public FibonacciResultsAdapter(Activity context, List<FibonacciItem> items) { m_Context = context; m_ResultItems = items; } public override long GetItemId(int position) { return position; } public override FibonacciItem this[int position] { get { return m_ResultItems[position]; } } public override int Count { get { return m_ResultItems.Count; } } public override View GetView(int position, View convertView, ViewGroup parent) { View view = convertView; if (view == null) view = m_Context.LayoutInflater.Inflate(Resource.Layout.ListViewItem, null); view.FindViewById<TextView>(Android.Resource.Id.txtOutput).Text = m_ResultItems[position].Value.ToString(); return view; } }
By implementing the adapter we removed the usage of Java type ArrayAdapter
, ArrayList
and the Java references to the FibonacciItem
instances.
The same applies to scenarios where native objects are being inherited in the managed domain. These, so-called, "special objects" are handled differently by the garbage collector. They have to be rescanned for all the references they carry with each garbage collection cycle.
Cyclic references (cycles)
Cyclic references occur, in general terms, when the underlying platform uses some type of reference counting as memory management strategy and the memory is cleaned up according to the number of references to that specific object instance.
Reference counting was abandoned by Microsoft with the release of .NET and the introduction of the generational tracing garbage collection. SGen in mono runtime on Android devices also uses some form of a mark and sweep algorithm. In both runtimes, the references are traced from so called "application roots". These objects are the ones that are "presumed" to be alive at the time of a garbage collection cycle.
The roots can be:
- References to global objects
- References to static objects
- References to static fields
- References on the stack to local objects
- References to objects waiting to be finalized
- References in CPU registers to objects on the managed heap
However, as mentioned before, on iOS, garbage collection was abandoned in favor of performance and yet ARC (automatic reference counting) fails to deal with what is called a retain cycle. Retain cycle occurs when the lower elements (aka children) in the creation hierarchy require references to the parent items. In this scenario, when the child or the parent sends a release
, the dealloc
methods never get to run since there is an extra reference keeping each of the items alive.
Figure 8: Retain Cycle
This native iOS problem becomes a problem in Xamarin applications when managed objects derive from native objects (that is, any object deriving from NSObect
) such as UI controls. When managed classes are inheriting from native objects, in order to keep them from getting garbage collected, Xamarin.iOS creates a GCHandle. These GCHandles, together with the managed references between the objects, create the described (indirect) retain cycle.
If we were dealing with a parent UIView
that holds an array of children and the child view objects that were retaining a reference to the parent object:
public class RetainingParentClass : UIView { public RetainingParentClass() { } } public class RetainingChildClass : UIView { private RetainingParentClass m_Parent; public RetainingChildClass(RetainingParentClass parent) { m_Parent = parent; } }
The following piece of code would create a retain cycle and would cause memory leaks in the application:
var parent = new RetainingParentClass(); parent.Add(new RetainingChildClass(parent));
If we were to execute this code in the constructor of a view, every time the application navigates to this view, we would be creating a new parent object, never to be garbage collected.
Figure 9: Instruments view for retained objects
In this case, the easiest fix would be to use a WeakReference
while we are creating a reference to the parent object from the child one. Using the weak reference avoids the retain cycle situations and does not interfere with the garbage collection.
public class RetainingChildClass : UIView { private WeakReference<RetainingParentClass> m_Parent; public RetainingChildClass(RetainingParentClass parent) { m_Parent = new WeakReference<RetainingParentClass>(parent); } }
Another option would be to implement IDisposable
interface to remove the strong link between the objects by setting the references to null before GC.