Выполняет ли сборщик мусора .NET прогнозный анализ кода? - PullRequest
30 голосов
/ 01 июля 2010

Хорошо, я понимаю, что этот вопрос может показаться странным, но я только заметил кое-что, что действительно озадачило меня ... Посмотрите на этот код:

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 жив: правда
о2 жив: правда

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

o1 жив: Ложь
о2 жив: Ложь

ПРИМЕЧАНИЕ: это происходит только тогда, когда код скомпилирован в режиме Release и работает вне отладчика

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

string s = o2.ToString();

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

o1 жив: Ложь
о2 жив: правда

Так что в этом случае o2 не собирается.

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

Ответы [ 4 ]

23 голосов
/ 01 июля 2010

Сборщик мусора опирается на информацию , скомпилированную в вашу сборку , предоставляемую 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();
        }
    }
}

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

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

Причина этого в том, что, хотя вы все ещеВыполнение внутри метода, связанного с объектом Test, в момент запроса GC на выполнение своей задачи, нет необходимости в каких-либо ссылках на экземпляр Test (нет ссылки на 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
    }

На этом этапе, что если Test не использует какие-либо данные экземпляра после получения копии неуправляемого дескриптора?Что если 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
}
12 голосов
/ 01 июля 2010

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

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

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

0 голосов
/ 06 января 2013

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

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

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

Да.

Что именно происходит?

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

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

0 голосов
/ 10 июля 2010

Я не эксперт по 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 привязка не сохраняется.

РЕДАКТИРОВАТЬ: Это оптимизация компилятора с именем постоянного сворачивания.Что может быть сделано с или без JIT.

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

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

ПРИМЕЧАНИЕ: это происходит только тогда, когда код компилируется в режиме Release и запускается вне отладчика.

это ключ.

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

...