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