2010-07-01 4 views
29

ОК, я понимаю, что вопрос может показаться странным, но я только что заметил то, что действительно озадачило меня ... Посмотрите на этот код:Выполняет ли сборщик мусора .NET прогностический анализ кода?

static void TestGC() 
{ 
     object o1 = new Object(); 
     object o2 = new Object(); 
     WeakReference w1 = new WeakReference(o1); 
     WeakReference w2 = new WeakReference(o2); 

     GC.Collect(); 

     Console.WriteLine("o1 is alive: {0}", w1.IsAlive); 
     Console.WriteLine("o2 is alive: {0}", w2.IsAlive); 
} 

С o1 и o2 все еще находятся в объеме, когда вывоз мусора происходит, я ожидал бы следующий вывод:

o1 жив: Правда
o2 жив: True

Но вместо этого, вот что я получил:

o1 жив: Ложные
o2 жив: Ложные

Примечание: это происходит только тогда, когда код компилируется в режиме выпуска и пробега вне отладчика

Я думаю, что GC обнаруживает, что o1 и o2 не будут использоваться снова, прежде чем они выходят из области видимости, и собирать с ними рано. Чтобы проверить эту гипотезу, я добавил следующую строку в конце TestGC метода:

string s = o2.ToString(); 

И я получил следующий вывод:

o1 жив: Ложные
o2 жив: Правда

В таком случае o2 не взимается.

Может ли кто-то пролить свет на то, что происходит? Связано ли это с оптимизацией JIT? Что происходит точно?

+5

Независимо от ответа, это классно. – jdmichal

ответ

21

Сборщик мусора опирается на информацию , собранную в вашу сборку , предоставленную компилятором JIT, в котором говорится, что код кода содержит различные переменные, а «вещи» все еще используются.

Таким образом, в вашем коде, поскольку вы больше не используете переменные объекта GC, их можно бесплатно собрать. WeakReference не помешает этому, на самом деле, это весь смысл WR, чтобы вы могли ссылаться на объект, не мешая ему собираться.

Случай около WeakReference объектов красиво суммируются в описании одной строки на MSDN:

Представляет слабую ссылку, которая ссылается на объект, в то же время позволяя, что объект, который будет утилизирован в процессе сборки мусора.

Объекты WeakReference не собираются в мусор, поэтому вы можете безопасно их использовать, но на объекты, на которые они ссылаются, осталась только ссылка WR, и, следовательно, их можно было бесплатно собрать.

При выполнении кода через отладчик переменные искусственно расширяются в области действия до тех пор, пока их область действия не закончится, как правило, конец блока, в котором они объявлены (например, методы), чтобы вы могли их проверить в точке останова.

Есть некоторые тонкие вещи, чтобы обнаружить с этим. Рассмотрим следующий код:

using System; 

namespace ConsoleApplication20 
{ 
    public class Test 
    { 
     public int Value; 

     ~Test() 
     { 
      Console.Out.WriteLine("Test collected"); 
     } 

     public void Execute() 
     { 
      Console.Out.WriteLine("The value of Value: " + Value); 

      GC.Collect(); 
      GC.WaitForPendingFinalizers(); 
      GC.Collect(); 

      Console.Out.WriteLine("Leaving Test.Execute"); 
     } 
    } 

    class Program 
    { 
     static void Main(string[] args) 
     { 
      Test t = new Test { Value = 15 }; 
      t.Execute(); 
     } 
    } 
} 

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

 
The value of Value: 15 
Test collected 
Leaving Test.Execute 

Причина этого заключается в том, что даже если вы по-прежнему выполняется внутри метода связанный с объектом Test, в момент запроса GC сделать это, нет необходимости в каких-либо ссылках на экземпляр для теста (нет ссылки на this или Value), и никаких вызовов какого-либо метода-экземпляра, оставшегося для выполнения, не требуется, поэтому объект безопасен для сбора.

Это может иметь некоторые неприятные побочные эффекты, если вы не знаете об этом.

Рассмотрим следующий класс:

public class ClassThatHoldsUnmanagedResource : IDisposable 
{ 
    private IntPtr _HandleToSomethingUnmanaged; 

    public ClassThatHoldsUnmanagedResource() 
    { 
     _HandleToSomethingUnmanaged = (... open file, whatever); 
    } 

    ~ClassThatHoldsUnmanagedResource() 
    { 
     Dispose(false); 
    } 

    public void Dispose() 
    { 
     Dispose(true); 
    } 

    protected virtual void Dispose(bool disposing) 
    { 
     (release unmanaged resource here); 
     ... rest of dispose 
    } 

    public void Test() 
    { 
     IntPtr local = _HandleToSomethingUnmanaged; 

     // DANGER! 

     ... access resource through local here 
    } 

На данный момент, что, если тест не использует какие-либо экземпляра данных после захвата копию неуправляемого ручки? Что делать, если GC теперь работает в точке, где я написал «ОПАСНОСТЬ»? Вы видите, где это происходит? Когда GC запускается, он выполнит финализатор, который выдержит доступ к неуправляемому ресурсу из теста Test, который все еще выполняется.

Неуправляемые ресурсы, обычно доступные через IntPtr или аналогичные, непрозрачны сборщику мусора, и они не учитывают их при оценке жизни объекта.

Другими словами, мы сохраняем ссылку на дескриптор в локальной переменной, не имеет смысла для GC, он отмечает только отсутствие ссылок на экземпляры и, следовательно, считает, что объект безопасен для сбора.

Это, если курс предполагает, что внешняя ссылка на объект, который по-прежнему считается «живым», отсутствует. Например, если вышеупомянутый класс использовался по методу, подобному этому:

public void DoSomething() 
{ 
    ClassThatHoldsUnmanagedResource = new ClassThatHoldsUnmanagedResource(); 
    ClassThatHoldsUnmanagedResource.Test(); 
} 

Тогда у вас есть такая же проблема.

(конечно, вы, вероятно, не следует использовать его, как это, так как он реализует IDisposable, вы должны использовать using -блок или позвонив по телефону Dispose вручную.)

Правильный способ написать выше метода состоит в том, что соблюдение GC не будет собирать наш объект, пока еще нужно:

public void Test() 
{ 
    IntPtr local = _HandleToSomethingUnmanaged; 

    ... access resource through local here 

    GC.KeepAlive(this); // won't be collected before this has executed 
} 
+0

Не следует ли игнорировать указатели 'WeakReference' для целей сбора мусора? – jdmichal

+0

@ Lasse, +1, очень поучительный ответ. Я жду немного, прежде чем я приму это, если появится дополнительная информация;). @jdmichal, действительно, и, по-видимому, они * игнорируются, так как я использую WR после 'GC.Collect' –

+0

. Слабые ссылки там, чтобы специально позволить вам хранить ссылку на объект, не мешая ему собираться. Таким образом, ссылка игнорируется при выполнении GC. Весь смысл WR состоит в том, чтобы гарантировать, что объекты, которые могут быть собраны *, могут быть собраны, но если они настолько дороги, чтобы создать, что вы предпочитаете избегать их сбора, используйте вместо этого обычную ссылку.Однако я не знаю, есть ли какая-либо специальная обработка слабых ссылок, т. Е. Если их шанс быть собранным отличается от обычных объектов. –

12

сборщик мусора получает пожизненные подсказки от JIT компилятором. Таким образом, он знает, что при вызове GC.Collect() нет более возможных ссылок на локальные переменные, и поэтому они могут быть собраны. Обзор GC.KeepAlive()

Когда подключен отладчик, оптимизация JIT отключена, и подсказка на всю жизнь расширяется до конца метода. Делает отладку намного проще.

Я написал гораздо более подробный ответ об этом, вы найдете it here.

+1

Это по существу тот же самый ответ, что и у Лассе, с заметной разницей: вы говорите, что намек на всю жизнь предоставляется JIT, но Лассе говорит, что это в сборке ... Кому я должен верить? ;). +1 в любом случае, поскольку он кажется достаточно близким к реальному объяснению. –

+3

Он прав насчет JIT, предоставляя эту информацию. Я отредактирую свой ответ. –

0

Я не эксперт C#, но я бы сказал, что это потому, что в производстве ваш код не оптимизируют в

static void TestGC() 
{ 
     WeakReference w1 = new WeakReference(new Object()); 
     WeakReference w2 = new WeakReference(new Object()); 

     GC.Collect(); 

     Console.WriteLine("o1 is alive: {0}", w1.IsAlive); 
     Console.WriteLine("o2 is alive: {0}", w2.IsAlive); 
} 

Нет больше o1, o2 связывания остаются.

EDIT: Это оптимизация компилятора с постоянной сгибанием. Что можно сделать с JIT или без него.

Если у вас есть способ отключить JIT, но сохранить оптимизацию выпуска, вы будете иметь такое же поведение.

Люди должны обратить внимание на примечание:

Примечание: это происходит только тогда, когда код компилируется в режиме выпуска и запуска вне отладчика

это ключ.

PS: Я также предполагаю, что вы понимаете, что означает WeakReference.

+0

Но 'w1' и' w2' все еще ссылаются на что-то, даже если вы заберете имя. Дело в том, что объекты с низкой ссылкой были собраны, когда компилятор может доказать, что они больше не будут использоваться. –

+0

Обратите внимание на то, что я говорю. Я не говорю о имени, я говорю о привязке. И ваши неправильные w1 и w2 не собираются, но точка объекта w1 и w2 есть. Если w1 и w2 были собраны, 'w2.IsAlive' будет генерировать исключение NullPointerException, которое не соответствует действительности. – mathk

0

Может кто-то пролил свет на то, что происходит?

Ваша ментальная модель того, как сбор мусора виртуальной машины собирает ресурсы, нереально проста. В частности, ваше предположение о том, что переменные и область действия как-то имеет отношение к сбору мусора, неверно.

Это связано с оптимизацией JIT?

Да.

Что происходит точно?

JIT не беспокоился о том, чтобы ссылки ссылались излишне, поэтому сборщик трассировки не нашел ссылок, когда он ударил его, чтобы он собрал объекты.

Обратите внимание, что в других ответах указано, что JIT передал эту информацию в GC, когда правда заключается в том, что JIT не передать эти ссылки на GC, потому что не было бы никакого смысла в этом.