Sunday, 23 September 2012

Avoiding Memory Leaks - Silverlight



RAM is a critical resource of computer. Even though modern desktops and even laptops are equipped with good amount of RAM, there is always a risk of memory leak.

Saving an Object on the Stack or the Heap
In .NET, there are two kinds of objects: Those held by value and those held by reference. To make things a little more complicated, there are also two kinds of memory in a computer: the stack and the heap.

Value types are simple types such as int, bool, double and other primitive types. These values belong to the object that created them, but no other object can reference them. Instead, if an object attempts to access them, a copy is created.

Reference types on the other hand can be referenced by multiple objects. A reference to such an object is sometimes known as a pointer (C++ programming language), although nowadays C# developers rather speak of a reference to an object. Reference types are always stored on the heap memory. This kind of memory is optimized for larger objects that have a longer lifetime.

On the other hand, the stack is a kind of memory that is optimized for faster access and frequent cleanups. Although the stack is easy to clean up, the heap requires special attention, with the help of the so-called garbage collector (GC).

Collecting Garbage and Leaking Memory
.NET is a managed environment, which means that the .NET application does not typically access the computer’s hardware directly. Instead, application programming interfaces (APIs) are used. For the developer, this is interesting because the risk of crashes is reduced compared to older technologies such as unmanaged MFC applications written in C++ for instance.

A good example of this is memory management. Developers writing in non-managed languages such as C/C++ had to manage memory manually, deleting unused objects explicitly and freeing the memory. In the contrary, the garbage collector used by .NET monitors periodically which objects are not used anymore, and removes them from memory. Garbage collection is a very fascinating topic: On one hand, the garbage collection must occur from time to time to avoid that the computer runs out of memory. On the other hand, collecting garbage is a slow process, and running the GC too often will slow down the application and the computer.

Finding Which Objects Can Be Collected On every cycle, the garbage collector is looking for objects that the application is not using anymore. In the great lines, here is how GC works:
- Starting at a root object, all the children, then the children’s children (and so on) are marked (typically by setting a flag on the objects). For instance, if an object has an array of other objects, these other objects are reachable and thus are marked.
- Of course, certain objects are marked multiple times because more than one object is keeping a reference to them. This is not a problem because if there is at least one reference to this object, it will be spared.
- Then, all objects are inspected. Those without the flag set are unreachable by any other object. They are collected, and the memory they were using is freed.

One can see why this process is taking time. Also, during the inspection, it is crucial that the tree of objects is not modified. During the garbage collection time, no other operation may run.

In the past few years, GC has been very much optimized and improved with new algorithms. Concepts like this of generation help the GC to identify faster which objects are more likely to be unused by the application. Fundamentally, however, one fact remains true: If an object holds a reference to another child object, the child is considered in use by the application. Note that this is the case even if the object has been forgotten and is not actually needed by the application anymore. It is easy to understand that keeping track of these references and attempting to free them when possible will help to optimize memory.

Freeing an Object
For the developer, freeing an object amounts to making sure that all the references to that object are set to null:
- For local variables inside a block (such as a loop, a method, and so on), the reference will automatically be set to null when the block is exited.
- For global variables, the reference should be set to null manually to make sure that the object can be garbage collected (given of course that no other references to that object are found in the application).
For example consider the classes
21
Note: Sorting Objects in Generations-In short, the concept of generation specifies that objects that “survived” garbage collection a first time will be assigned to generation 1, and will be visited less often by the garbage collector than new objects (in generation 0). Similarly, objects that survived a generation 1 garbage collection will be assigned to generation 2, and will be visited even less often. This speeds up the garbage collection by assuming that older objects are less likely to be modified often than new objects.


public class MyCustomObject
 {
 private MyChildObject _child;
 public MyCustomObject(MyChildObject child)
 {
 _child = child;
 }
 // ...
 }

 public class MyOtherObject
 {
 private MyChildObject _child;
 public MyOtherObject(MyChildObject child)
 {
 _child = child;
 }
 // ...
 }

 public class MyChildObject
 {
 // ...
 }

Creating and Freeing the Classes

 public partial class MainPage : UserControl
 {
 private MyCustomObject _custom;
 private MyOtherObject _other;

 public MainPage()
 {
 InitializeComponent();

 var child = new MyChildObject();
 _custom = new MyCustomObject(child);
 _other = new MyOtherObject(child);
 // ...
 _other = null; // free the reference
 }
 }

- Both _custom and _other are kept as reference by the MainPage class.
- An instance of MyChildObject is created. Because it is local, this reference will be deleted when the block exits; that is when the constructor finishes execution.
- However, this instance has been passed to the MyCustomObject and to the
MyOtherObject constructors who in turn store a reference to that instance.
- Reference named _other is set to null explicitly. Because no one else keeps a reference to that instance, it will be collected the next time that the GC runs. However, the instance of MyChildObject is still referenced by the MyCustomObject instance, and therefore it cannot be freed.

Here we see that it is critical to explicitly free large objects by setting all their references to null, so that the GC considers them as unused. Forgetting even just one reference will prevent the memory to be freed.

Living a Shorter Life
 In-browser Silverlight applications typically have a shorter life than, for example, WPF applications running on the desktop. Because of this, the risk of getting a really critical memory leak in an in-browser Silverlight application is smaller: When the web page hosting the plug in is either refreshed or navigated away from, the memory is cleaned up and the computer is not at risk.


However, Silverlight 4 and the new advanced OOB applications are much more similar to desktop applications than to web applications. Their lifetime is typically longer, and a memory leak in these applications is much more dangerous. Generally speaking, it is good to remember that Silverlight applications are rich applications, and that regardless of where and how they are running, memory leaks should be avoided absolutely.

Unregistering Event Handlers
A typical mistake done by .NET programmers is related to event handling -

 public class MyObjectWithEvents
 {
 public event EventHandler<EventArgs> SomethingHappened;
 // ...
 }

 public partial class MainPage : UserControl
 {
 public MainPage()
 {
 InitializeComponent();
 var myObject = new MyObjectWithEvents();
 myObject.SomethingHappened += HandleSomethingHappened;
 // …
 }

 private void HandleSomethingHappened(object sender, EventArgs e)
 {
 // …
 }
 }
- An instance of MyObjectWithEvents is created. Note that it is stored as a local variable, and therefore will be garbage collected as soon as the block (the MainPage constructor) is finished executing.
- Next assigned an event handler (the HandleSomethingChanged method) to the SomethingChanged event of the MyObjectWithEvents instance.

This simple operation caused a potential memory leak: An event handler is creating a strong reference between the object that raises the event (in that case, the myObject instance) and the object that handles the event (MainPage). When the constructor exits, myObject cannot be collected for deletion by the GC because of this strong reference.
To solve the issue, the event handler must be removed before the block exits with the line of code as below –

myObject.SomethingHappened -= HandleSomethingHappened;

Properly removing all the event handlers on unused references before exiting a block or before a class is deleted is a good practice that reduces the memory leaks and ensures that your application will run more smoothly and provide a better user experience.

Using Commands Instead
In contrast to events, the Command property of a Button control is set through a Binding, which creates a less strong dependency between two objects. Even though the Command references another object (for example, a view-model), this does not prevent the viewmodel object to be garbage collected if needed. Using commands rather than events is a good practice because of the looser dependency that it creates between two objects.

Disposing Objects
Another kind of objects should be cleaned up after the application is done using them: the types implementing the IDisposable interface. When an object is defined as
IDisposable by its developer, it is a clear indication that this object might have used resources that should be properly cleaned up. For example, this might be an access to the file system (for the Stream, StreamReader, StreamWriter, and other classes), a connection to a database, and so forth. Closing and disposing such objects used to be an annoying process but in more recent versions of .NET (and in Silverlight), it has become very easy thanks to the using keyword. For example, a StreamReader and its Stream are automatically closed and disposed by the code –

var dialog = new OpenFileDialog();
if (dialog.ShowDialog() == true)
{
using (var stream = dialog.File.OpenRead())
{
using (var reader = new StreamReader(stream))
{
       // Read the file
}
}
}

Using Weak References
One way to avoid strong references between objects is to use the WeakReference class. This special class keeps a reference to an object but does not prevent it to be garbage collected.
To avoid blocking the MyChildObject instance we can do the following - 

 public class MyCustomObject
 {
 private WeakReference _childReference;
 public MyCustomObject(MyChildObject child)
 {
            _childReference = new WeakReference(child);
 }
 // ...

 public void UseChild()
 {
 if (_childReference != null  && _childReference.IsAlive)
 {
             (_childReference.Target as MyChildObject).DoSomething();
 }
 }
 }
- Line 3 declares a WeakReference instead of storing the instance of MyChildObject as an attribute directly.
- Line 7 constructs a new WeakReference instance and passes it the child.
- Finally, when the child needs to be used, line 15 makes sure that the MyChildObject instance is still alive. Because WeakReference does not prevent the GC to delete the child object, this step is necessary to avoid calling disposed objects.
- If the object is still alive, the Target property of the WeakReference is used to access the stored object.

Using a WeakReference is more convoluted than using an attribute directly. Although this class is very useful in some cases, it must be used with care. In many cases indeed, an object referenced by another object should not be allowed to be deleted. This is exactly the intent of the reference: to signal that the referenced object is still needed. Instead, the WeakReference class can be used when two objects should have a way to communicate, but neither object is responsible for the other object’s lifetime.

Finding a Leak
If, in spite of all the precautions mentioned in this section, a memory leak is suspected in an application, you can use the WinDbg utility to find the cause. Also, Visual Studio Premium and Ultimate support some limited profiling of memory garbage collection as well as allocation and object lifetime.

No comments:

Post a Comment