2011-05-24 52 views
9

我有一个循环变量,虽然超出了范围,但似乎并没有收集垃圾(根据Red-Gate ANTS内存分析器)。循环变量没有收集到

的代码看起来是这样的:

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

据我所知,到item参考保持,直到blockingQueue.dequeue()回报。这是预期的行为,还是它可能是内存分析器中的一个错误?其次,如果这是预期行为,我将如何强制item在循环体的末尾收集?将其设置为null似乎不会导致它被收集。这很重要,因为队列可能会长时间阻塞,并且item引用相当大的对象树。

注意,分析器的文档说在执行内存快照之前执行GC,并且该引用不在终结器队列中。

我能够重现与代码here相同的问题。

更新

在要点中的代码略有缺陷在于合法地GetFoo()举行的一个参考。改变它后,对象确实现在得到收集时明确设置为null。但是,我相信汉斯的回答解释了我在实际代码中看到的情况。

+3

没有别的东西有它的参考? – 2011-05-24 03:52:57

+0

你能显示项目的所有用法吗? – 2011-05-24 04:04:02

+0

分析器显示对GC根目标的引用,这是其中之一。我在github上发布的例子显示了这个问题,并且在循环中有* only *引用。 – SimonC 2011-05-24 04:19:31

回答

7

抖动优化器可能是此问题的根源。下面是一个例子:

class Program { 
    static void Main(string[] args) { 
     while (true) { 
      var input = Console.ReadLine(); 
      Console.WriteLine(input); 
      input = null; 
     } 
    } 
} 

生成此机器代码:

  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 

ESI寄存器存储输入变量。注意它从来没有被设置回null,它总是存储对最后输入的字符串的引用。优化器已删除空分配语句。垃圾收集器从抖动中获得生命提示,它会说在循环期间引用是活动的。

问题发生在第二次和随后的传递,当你从不输入东西,然后ReadLine()将阻塞(类似于你的阻塞队列),esi寄存器值继续引用字符串。在循环过程中,它永远不会被垃圾收集,至少在重新分配之前。

有没有干净的修复。这里是一个丑陋的一个:

[MethodImpl(MethodImplOptions.NoInlining)] 
    public static void NullReference<T>(ref T obj) where T : class { 
     obj = null; 
    } 

及用途:

 while (true) { 
      var input = Console.ReadLine(); 
      Console.WriteLine(input); 
      NullReference(ref input); 
     } 
1

直到调用了Dequeue,那么item的值还没有被覆盖,并且仍在使用中是否正确?你可以做的最好的办法是将其设置为null,即调用GC.Collect(),但不保证收集该变量,也无法强制收集它,那么为什么要这么做呢?

+0

我很费心,因为在我的原因中,出队可能需要很长时间,并且该项目保存了大型对象树的引用。我现在所能做的最好的就是调用'item.Dispose()',并希望它在那里删除对它的对象的引用(引用的类是第三方GUI组件,所以我无法控制它)。 – SimonC 2011-05-24 04:22:18

0
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 
} 

我怀疑将它放在一个块中可能工作。如果是一次性的,你可以

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

我可能误解了你,但这里的另一种可能性来处理另一种情况:

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

如果您正在与该项目完成后,你可以释放你参考吧在循环体的末尾:

item = null; 

至于垃圾收集它,无论大项目如何,是否有它和垃圾收集器的任何其它引用了不是C选择它,然后垃圾收集器不认为它需要收集。

让垃圾收集器做好工作。它会在适当的时间收集东西,并且它会高效地完成工作,从内存和时间两方面进行交易。

0

我认为问题是item永远不会超出范围,直到循环结束。 GC不够智能,无法识别item中的值在被覆盖之前不会被使用,因此无法收集。

完成后只需将其设置为null将删除最后一个引用并允许收集对象。

0

好以下两个代码剪产生相同的IL:

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

现在试图依靠词汇范围,以释放在X的本地裁判:

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

结果是相同的这两种情况。当循环的迭代结束时,保存名为x的ref的本地地址不会被清零。即使我们未能分支到循环的开始,本地也是从不将设置为空。相反,当机会出现时,编译器会重用这个局部变量槽。

如果您明确地将x设置为null,编译器将发出il以将本地设置为null,即使您已启用优化标志。如果这是优化出来的,它在JIT中不是静态编译器。