2014-06-11 2 views
4

Если у меня есть цикл, такие как это:Вывоз мусора из труднодоступных объектов в цикле

public class Foo { 
    public Foo Foo; 

    public Foo() { 
    } 
} 

class Program { 
    public static void Main(string[] args) { 
     var foo = new Foo(); 
     long i = 0; 
     while(i < Int64.MaxValue) { 
      foo.Foo = new Foo(); 
      foo = foo.Foo; 
      if(i % 10000 == 0) 
       GC.Collect(); 
      i++; 
     } 
     GC.Collect(); 
    } 
} 

сборщик мусора не будет убирать родительские объекты до тех пор, пока цикл завершается. Почему это? Я не вижу никакого способа ссылаться на них из кода, как только foo переназначается, поэтому их не следует очищать?

Я изучал использование памяти в диспетчере задач после передачи некоторых точек останова, которые я установил, чтобы определить, что это происходит. Он продолжает расти внутри цикла (до нескольких ГБ, если я делаю это бесконечным), но сразу бросается, когда цикл завершается, и вызывается второй вызов GC.Collect().

+1

Какие "родительские объекты"? –

+2

«Сборщик мусора не будет очищать родительские объекты до тех пор, пока цикл не будет завершен»: можете ли вы описать в своем вопросе, как вы делаете эти наблюдения? – quantdev

+0

@quantdev Я редактировал вопрос. – joelises

ответ

6

Вот слегка измененная программа, которая демонстрирует поведение более четко:

class Foo 
{ 
    public int Value; 
    public Foo Next; 

    public Foo(int value) { this.Value = value; Console.WriteLine("Created " + this.Value); } 
    ~Foo() { Console.WriteLine("Finalized " + this.Value); } 
} 

class Program 
{ 
    public static void Main(string[] args) 
    { 
     var foo = new Foo(0); 
     for (int value = 1; value < 50; ++value) 
     { 
      foo.Next = new Foo(value); 
      foo = foo.Next; 
      if (value % 10 == 0) 
      { 
       Console.WriteLine("Collecting..."); 
       GC.Collect(); 
       Thread.Sleep(10); 
      } 
     } 
     Console.WriteLine("Exiting"); 
    } 
} 

на .NET 4.5, когда я построю в режиме отладки И цели Любой процессор или x86, я воспроизвести поведение вы находитесь видение: экземпляры не завершаются до тех пор, пока не будет напечатана «Выход». Но когда я строй в режиме выпуска ИЛИ целевого x64 (даже при создании в режиме отладки), экземпляры будут завершены, как только они недостижимы:

Created 0 
Created 1 
Created 2 
Created 3 
Created 4 
Created 5 
Created 6 
Created 7 
Created 8 
Created 9 
Created 10 
Collecting... 
Finalized 9 
Finalized 0 
Finalized 8 
Finalized 7 
Finalized 6 
Finalized 5 
Finalized 4 
Finalized 3 
Finalized 2 
Finalized 1 
Created 11 
Created 12 
Created 13 
... 

Почему это происходит? Я полагаю, что только эксперт по CLR может сказать нам точно, но вот мое предположение: поведение зависит от конкретных деталей машинного кода, которые генерируют компилятор и оптимизатор JIT, детали, которые зависят от набора целевых команд и того, re работает в режиме отладки. (Более того, эти данные могут хорошо измениться в будущих версиях среды выполнения.) В частности, в случае x86/Debug я думаю, что первый экземпляр Foo(0) спрятан в переменную реестра или стека, которая никогда не будет перезаписана в остальной части метод; этот исходный экземпляр поддерживает всю цепочку. В случаях x86/Release и x64 я считаю, что из-за оптимизации JIT такая же переменная реестра или стека используется для каждого экземпляра, тем самым освобождая исходный экземпляр.

+0

Дополнительная информация о * how/why * поведение отличается в режиме отладки? – user2864740

+3

@ user2864740 компилятор/JIT генерирует дополнительные временные интервалы, чтобы помочь в отладке. Вы не хотели бы, чтобы ваши местные жители собрались, когда вы смотрели на них в окне «Часы». –

+0

True .. но похоже, что ему нужно будет хранить коллекцию (а не только один или два дополнительных темпа/объекта), чтобы сохранить сильную доступность объектов в этом случае: | – user2864740

Смежные вопросы