I have a loop variable that does not appear to be getting garbage collected (according to Red--Gate ANTS memory profiler) despite having gone out of scope.
The code looks something like this:
while (true)
{
var item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue
// do something with item
}
As far as I can tell, a reference to item
remains until blockingQueue.dequeue()
returns. Is this intended behaviour, or could it be a bug in the memory profiler?
Secondly, if this is intended behaviour how would I force item
to get collected at the end of the loop body? Setting it to null
does not appear to cause it to get collected. This is important as the queue could potentially block for a long time and item
references a fairly large object tree.
Note, the documentation of the profiler says that a GC is performed before taking a memory snapshot, and the reference is not on the finalizer queue.
I was able to reproduce the same problem with the code here.
Update
The code in the gist was slightly flawed in that it legitimately held on to a reference in GetFoo()
. Having changed it the object does now get collected when explicitly set to null
. However, I believe Hans' answer explains the situation I'm seeing in my actual code.
Until Dequeue
is called, then the value of item has not been overwritten and is still in use correct? The best you could do is set it to null, the call GC.Collect(), but you aren't guaranteed to have that variable collected, and no way to force it to be collected, so why bother?
The jitter optimizer is the likely source for this problem. Here's an example:
class Program {
static void Main(string[] args) {
while (true) {
var input = Console.ReadLine();
Console.WriteLine(input);
input = null;
}
}
}
Generates this machine code:
while (true) {
var input = Console.ReadLine();
00000000 push ebp ; setup stack
00000001 mov ebp,esp
00000003 push esi
00000004 call 6E0208F0 ; Console.In property getter
00000009 mov ecx,eax
0000000b mov eax,dword ptr [ecx]
0000000d call dword ptr [eax+64h] ; TextReader.ReadLine()
00000010 mov esi,eax ; assign input variable
Console.WriteLine(input);
00000012 call 6DB7BE38 ; Console.Out property getter
00000017 mov ecx,eax
00000019 mov edx,esi
0000001b mov eax,dword ptr [ecx]
0000001d call dword ptr [eax+000000D8h] ; TextWriter.WriteLine()
00000023 jmp 00000004 ; repeat, note the missing null assigment
The esi register stores the input variable. Note how it is never set back to null, it always stores a reference to the last entered string. The optimizer has removed the null assignment statement. The garbage collector gets lifetime hints from the jitter, it will say that the reference is live for the duration of the loop.
The problem occurs on the second and subsequent pass, when you never type something then ReadLine() will block (similar to your blocking queue) and the esi register value continues referencing the string. It will never be garbage collected for the duration of the loop, at least until it gets reassigned.
There's no clean fix for this. Here's an ugly one:
[MethodImpl(MethodImplOptions.NoInlining)]
public static void NullReference<T>(ref T obj) where T : class {
obj = null;
}
and use:
while (true) {
var input = Console.ReadLine();
Console.WriteLine(input);
NullReference(ref input);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With