In the previous post, I looked at tracking down and fixing a memory leak in ANTS Performance Profiler; .NET memory usage gradually increased over time when different regions of the timeline are selected while the “Database Calls” view is open.
When I left off, I’d found one problem – a dictionary mapping UI components to data models wasn’t being cleared when the UI was updated, so the data models were kept in memory when they were no longer needed – but it seemed there was still a leak. Profiling after my initial fix gave the following summary screen in ANTS Memory Profiler:
There is still a large increase in the memory used by StackTrace
classes between the two snapshots. Selecting the StackTrace
class and opening the “Instance Categoriser” view, this time ANTS Memory Profiler shows me two reference chains keeping StackTrace
objects in memory:
Narrowing it down
I need to work out which of these is the cause of my problem: I could follow both chains and see which one seemed unusual, but ANTS Memory Profiler can help me out here – I know there has been an increase in memory used by StackTrace
objects, and I know from my understanding of the code that when a new region of the timeline is selected, data for that new region is read from disc, new StackTrace
objects are constructed and the old ones should be cleaned up – so, if StackTrace
objects from before I selected a new region of the timeline are still hanging around, those are likely to be my problem. ANTS Memory Profiler allows me to view the reference chains for just these objects using the “Filters” panel:
Here, I chose to filter my results to show only objects from my first snapshot that are still alive in my second – that is, the StackTrace
objects I think should have been cleared up. After applying the filter, ANTS Memory Profiler now shows me only one reference chain in the Instance Categoriser:
This chain of references should tell me what’s holding on to those StackTrace
objects from the previous snapshot. Scrolling back, I can see it’s still a SqlTableQueryRow
causing the problem – but the class keeping that alive looks interesting:
This class – <>c__DisplayClass7
– is a compiler-generated class, and the object referencing it is a Func<IProfileRange, IInternalSourcCodeViewModel>
.
Anonymous functions in C# – a quick primer
This pattern of code is typical of that generated by the compiler when compiling an anonymous function – either a lambda expression or an anonymous method – that captures some of its surrounding context. For example:
1 2 3 4 5 6 |
private static Action<Data> GetProcessResultFunction(string s) { var result = GetResult(s); Action<Data> myFunction = data => result.Process(data); return myFunction; } |
In this code, the lambda expression assigned to myFunction
references the local variable result
in that method, so when myFunction
is returned to the caller, a reference to result
must be maintained so that the Process
method can be invoked on it when myFunction
is called. The C# compiler achieves this by creating a class behind the scenes: this class has a single method representing the anonymous function, and members for the captured variables. For example, in the above case, the class would look something like this when decompiled with.NET Reflector:
1 2 3 4 5 6 7 8 9 |
private sealed class <>c__DisplayClass1 { public Result result; public void <GetProcessResultFunction>b__0(Data data) { result.Process(data); } } |
And the actual code for generated the GetProcessResultFunction
method will look something like this when decompiled:
1 2 3 4 5 6 |
private static Action<Data> GetProcessResultFunction(string s) { <>c__DisplayClass1 class2 = new <>c__DisplayClass1(); class2.result = GetResult(s); return new Action<Data>(class2.<GetProcessResultFunction>b__0); } |
The generated code creates an instance of <>c__DisplayClass1
, sets the result
field to the local result, and then construct an Action<Data>
, passing the instance member function b__0
to the constructor.
The Action<>
constructor takes a delegate – under the hood, this is a tuple of a function pointer and an optional object reference (and optionally a reference to another delegate, but that is unused here), and in this case, it will be a reference to the <>c__DisplayClass1
instance, and a pointer to its <GetProcessResultFunction>b__0()
method.
It’s easy to see how this can cause leaks – an anonymous function is created and this causes an object to be captured by the compiler-generated class, intentionally or unintentionally, which prevents that object from being freed up when it later falls out of scope.
The problem
Returning to the leak in ANTS Performance Profiler and scrolling back a bit further in the instance categoriser, I get to:
In ANTS Performance Profiler, there is the option to show the user source code for the method or SQL query they currently have selected. Source code is also cached, but indirectly – that is, rather than caching the code directly, it caches a function that can be called to generate the source for a particular piece of C# source or SQL query.
As source code can sometimes be both very large and expensive to generate (for example, when decompiling code for which source is not available, using the built-in .NET Reflector), we need to be able to adopt different strategies for caching/generating it, and this approach permits that – the SourceCodeViewModelRepository
is a collection of these methods for generating that source. The interface for a class which can provide one of these functions is:
1 |
Func<IProfileRange, IInternalSourceCodeViewModel> BuildModel { get; } |
That is, it returns a function that given an IProfileRange
, returns an IInternalSourceCodeViewModel
.
So, what it looks like here is that the function that has been created to generate the source for a particular SQL query has also captured the SqlTableQueryRow
object representing that query in the “Database calls” table. The SqlTableQueryRow
has a reference to a ProfileRange
, and this in turn has references to a number of StackTraces
(see the previous article for more on this).
Looking at the source for BuildModel
in SqlTableQueryRow
:
1 2 3 4 5 6 7 8 9 10 11 12 |
public Func<IProfileRange, IInternalSourceCodeViewModel> BuildModel { get { var source = new WeakReference<string> (GenerateSql(m_RowData.SqlEventData)); return range => EventSourceHelper.GenerateSqlSourceCodeViewModel( source, range, m_RowData.SequenceHandle, GenerateSql); } } |
There’s a few things going on here. First of all, this method actually generates the source code for the event using the GenerateSql
method (which takes a SqlEventData
object), but stores it in a WeakReference
. The method EventSourceHelper.GenerateSqlSourceCodeViewModel
checks this WeakReference
and if it’s still alive, returns it; otherwise, it looks through the supplied profile range for a SqlEventData
with the given sequence handle, and calls GenerateSql
on that event.
The reasoning behind this slightly complex mechanism is that it’s likely we’ll want the source soon after requesting the builder function. However we don’t want to keep it hanging around unnecessarily (especially as some source code can be very large), so we can let it get garbage collected – and we can use the builder function we’ve cached to rebuild it if needed.
This BuildModel
property wraps all that up in a single anonymous function that takes an IProfileRange
and returns an IInternalSourceCodeViewModel
– the caller doesn’t need to know about the event handle or SqlEventData
at all. Very neat.
Unfortunately, this code is causing a memory leak: the function appears to be capturing some context that keeps a bunch of StackTraces
alive, via a ProfileRange
held onto by a SqlTableQueryRow
. The hint as to what is causing the problem is the fact that the anonymous function is generated by SqlTableQueryRow
itself – that is, the function appears to be capturing the object that created it – this
.
Looking at the anonymous function, it calls another static method with four parameters:
source
, which is a local variable – we would expect this to be captured by the compiler-generated class, but it doesn’t need to include a reference tothis
– it’s just aWeakReference
to a string. This shouldn’t be the problem.range
, which is a parameter to the anonymous function – this doesn’t form part of the capture, so this can’t be the problem.m_RowData.SequenceHandle
, a property on a member variable (of typeint
).GenerateSql
, the method onSqlTableQueryRow
used to generate the SQL string.
So 3 and 4 seem likely candidates – it’s worth taking them each in turn.
GenerateSql method
It’s pretty clear looking at the method signature what’s going on:
1 |
private string GenerateSql(ISqlEventData sqlEventData); |
The important thing to note is that this is an instance method: it requires an instance of an object to execute – this
. When the capture class is generated, a reference to this
will be included so that GenerateSql
can be called on it.
Fortunately, the code for GenerateSql
doesn’t appear to reference the instance at all – I can turn this into a static
method without any problems.
1 |
private static string GenerateSql(ISqlEventData sqlEventData); |
A static
method doesn’t require an instance to execute, so this
no longer needs to be captured in this case, and this part of the leak is now fixed.
The member variable/property reference
In order to access the member variable m_RowData
(and therefore its SequenceHandle
property) later on, the compiler-generated class needs to capture the object it’s a member of – i.e., this
. The compiler can’t know that the value won’t change between the anonymous function being constructed and it being executed, so it has to capture the reference, and get the value of the property when the function is invoked. This is clearly one of the potential sources of my leaks – but the capture seems to be for a legitimate reason, so what can I do?
Well, the compiler can’t know that the property value isn’t going to change, but I know that the row data for a SqlTableQueryRow
is readonly
, as is the SequenceHandle
property on the row data – so I know the value of SequenceHandle
(which is an int
) cannot change between this anonymous function being created and invoked. That means that rather than passing in a property-read to the function, I can do a by-value copy and pass that in instead:
1 2 3 4 5 |
var source = new WeakReference<string>(GenerateSql(m_RowData.SqlEventData)); var sequenceHandle = m_RowData.SequenceHandle; return range => EventSourceHelper.GenerateSqlSourceCodeViewModel( source, range, sequenceHandle, GenerateSql); |
To make this even more explicit, I can factor out the creation of the function into another method:
1 2 3 4 5 6 7 8 9 10 11 |
var source = new WeakReference<string>(GenerateSql(m_RowData.SqlEventData)); return CreateBuildModelFunction(source, m_RowData.SequenceHandle); [...] private static Func<IProfileRange, IInternalSourceCodeViewModel> CreateBuildModelFunction(WeakReference<string> source, int sequenceHandle) { return range => EventSourceHelper.GenerateSqlSourceCodeViewModel( source, range, sequenceHandle, GenerateSql); } |
The method that is actually creating the anonymous function is now a static
method, and its parameters are just a WeakReference
and an int
; I can now be sure that the anonymous function itself isn’t capturing the outer SqlTableQueryRow
class – which will, in turn, mean that the ProfileRange
for that SqlTableQueryRow
and all its associated StackTraces
are no longer held in memory by this function.
Results
The final profiling results after making the above changes confirm that this has been worth it: StackTrace
is no longer the biggest user of memory, and the difference in the amount of memory used between the two snapshots is now greatly reduced, as is the overall change in memory use – down from 97MB to 9.5MB:
Conclusion
Over these two posts, I walked through the process I went through finding and fixing some memory leaks in ANTS Performance Profiler that would have been very hard to identify purely by hand. ANTS Memory Profiler helped me identify the classes that were not being cleaned up when I expected them to be, and the helped me identify the reason these classes were being held in memory – in one case, a look-up table used for mapping user interactions to underlying data models wasn’t being cleared when the UI was updated, and in another, the compiler-generated backing class for an anonymous function was capturing the object that created it.
Load comments