开发者

Loop variable not getting collected

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 开发者_C百科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.


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);
        }


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?


while (true)
{
    {
        var item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue
        // do something with item
    }
    // do others that might be blocking for a long time
}

I suspect enclosing it in a block might work. If it's disposable, you could

while (true)
{
    using (var item = blockingQueue.dequeue(); 
    {
        // do something with item
    }
    // do others that might be blocking for a long time
}

I may have misunderstood you, but here's another possibility to handle another situation:

while (true)
{
    var item = null;
    item = blockingQueue.dequeue(); // blocks until an item is added to blockingQueue
    // do something with item
    item = null;
}


If you are done with the item, you can release your reference to it at the end of the body of the loop:

item = null;

As far as garbage-collecting it, no matter how big item is, if there are no other references to it and the garbage collector has not collected it, then the garbage collector doesn't think it needs to be collected yet.

Let the garbage collector do its job. It will collect things in due time and it will do so efficiently, trading off both memory and time.


I think the problem is that item never goes out of scope until the loop ends. The GC is not smart enough to recognize that the value in item is not going to be used before it's overwritten, so it can't collect it.

Just setting it to null when you're done with it will remove the last reference and allow your object to be collected.


Well the following two code snips produces the same il:

int i = 0;
System.Object x;
while(i < 100){
    x = new System.Object();
    System.Console.WriteLine(x.ToString());
    i++;
}

Now trying to depend on lexical scoping to free the local ref in x:

int i = 0;
while(i < 100){
    System.Object x = new System.Object();
    System.Console.WriteLine(x.ToString());
    i++;
}

The result is the same in both cases. The local holding the ref named x is not nulled when an iteration of the loop ends. Even if we fail to branch to the start of the loop the local is never set to null. Instead the compiler will reuses this local variable slot when an opportunity arises.

If you explicitly set x to null the compiler will emit il to set the local to null even if you have optimization flags on. If that is optimized out it's happing in the JIT not the static compiler.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜