Solving memory leaks using Flash builder 4 profiler
Locating memory leaks section in FB4 docs provides an overview and few techniques for handling this issue.
In this article I would like to offer another technique for using the object references view to quickly track down the root of a memory leak, straight down to the exact line of code causing it.
1. Choose a repeatable sequence.
2. Use the "Generate object allocation stack traces" checkbox when starting the profiling session.
3. Execute the sequence several times, and take a first memory snapshot. Execute it few times more and take a second snapshot.
4. Locate a class whose instances are not being GCed.
5. Open this class in the object references view for both snapshots.
6. Expand the first instance's back reference list, and look for suspicious path.
7. Starting from top to root, go through the path, and for each object compare its instance count for both snapshots.
The instance whose count is the same for both snapshots is causing the memory leak, and accumulates instances of the object it holds a reference to. it is a one-to-many-reference-holder.
8. By looking at the Allocation Trace view for the accumulated object, you can find where in the code the reference was added to it.
9. Make sure the reference is removed.
I assume you are already familiar with this view, and got to the point where you know which the object not being GCed is, and needs to find out who's preventing it from being released.
In an average application, after running a sequence of steps a few times, you may find yourself with hundreds of instances of the same class, each has hundreds of back references listed in the object references view, which can be quite overwhelming.
Where do you start? What should you look for?
You came to the right place! I will show you a very quick and simple way to spot the bug:
Before I start, there is an important concept you should get familiar with. I call it "the one to many reference holder".
The idea is quite simple, and is best visualized by an illustration:
Let's say you have a memory leak which results with 3 instances of the same object (class A) in the first snapshot, and 6 instances in the second snapshot (taken after repeating the same sequence of steps few more times) . A possible topology of these objects references may look like this in the first and second snapshots:
In the above illustration, all three instances are referenced by a single object (of class B), which is referenced up to the GC root.
Class B is a one–to-many-reference-holder,and is a good candidate for causing a memory leak. In case the number of class A instances keeps growing while you repeat the scenario, then class B has a memory leak.
Another possible reference topology may end up with the same result of 3 and 6 instances:
This time, the 3 instances are held by other 3 instances (class B), which in turn are held, again, by a single object (class C), which is held up to the root. In this topology, although class B does hold a reference to class A, we can clearly see that it is pointless to deal with it, since class C is the one accumulating references. He is a one-to-many-reference-holder and causes the memory leak. If we deal with class C, the class B instances along with class A instances are all gone.
Having this concept clear, answers the question "what do we look for?".
Although FB4 does not have this nice topology view for object references (Mihai Corlan kindly pointed me to Grant skinner's article on the flash sampler which might be used for allowing this kind of view), you'll see that finding these one-to-many-reference-holders is a matter of minutes. In the "object references" view, you open a suspected path to the object, and start following it to the root, checking the instance count of each object on this path.
Let's look on a real life sample:
Looking at this view(part of the namespaces is hidden for client's privacy sake), we see the following classes along this path:
DatagridItemRenderer, CoverageMetricRenderer, SummaryFooter, ConfigurableDataGrid, DataGridContent, ProfilerExtendedQueryResultWrapper , TransactionclassesProxy and so on.
We then go back to the Memory snapshot view, sort it by class name, and check the number of instances of each class (Instances column).
The following table summarizes the number instances of the above mentioned classes for two snapshots (the second taken after few more scenario repeats):
||Snapshot #1 instances count||snapshot #2 instances count|
The picture become very clear now: ProfilerExtendedQueryResultWrapper remains with the same number of instances between snapshots, but the number of instances (of class DataGridContent) it holds, keeps growing.
This is our one-to-many-reference-holder !
Resolving this will clear this path from this point up.
Let's take a closer look at the suspected area:
From looking at the object references view, we can see that ProfilerExtendedQueryResultWrapper has a property named _entries, which is of type ArrayCollection. This property accumulates references to DataGridContent instances.
When you see the [savedThis] and [listener0] properties, it's an indication of reference made by adding event listeners, and should be read as follows:
One of DataGridContent's functions was added as an event handler to an event dispatched by _entries property of ProfilerExtendedQueryResultWrapper.
As a result, _entries holds a reference to DataGridContent instance through one of it's functions.
In case you are familiar with the code, it will probably take you few minutes to locate where exactly in the code does this happen.
However, FB4 can take you to the exact line of code, relieving you from the task of looking for it by yourself:
In order to let FB4 find the buggy line of code, you should check the "Generate object allocation stack traces" checkbox when starting the profiler session:
Turning this feature on, allows you to see where in the code was an instance created.
In this case, the instance of interest is the instance of the function added to the arrayCollection's list of event listeners.
Clicking on the "Function" row will show you it's allocation trace on the right "Allocation trace" pane. Clicking on the topmost line of this trace will open the code editor on the line where this function instance was created (line 99 in DataGridContent class):
Let's examine this code excerpt:
allResults = (value as IProfilerResult).entries; allResults.addEventListener(CollectionEvent.COLLECTION_CHANGE,handleCollectionChange)
We can see that handleCollectionChange function, which belongs to DataGridContent class, is being added as a handler to the collection change event of the entries property.
As it turns out, this event listener is not removed, and the entries property does not belong to a child of DataGridContent (adding an event listener to your child does not cause memory leaks due to the GC sweep mechanism).
This is our memory leak bug !
Resolving event listener memory leaks:
A memory leak caused by an event listener can be solved by one of these ways:
1. Take care of removing the event listener when it is no longer needed.
2. Use the useWeakReference parameter to create a weak event listener .
In our case changing the code to the following solved the problem:
allResults.addEventListener(CollectionEvent.COLLECTION_CHANGE,handleCollectionChange,false,0, true );