Объекты в массивах не собирают мусор - PullRequest
6 голосов
/ 11 ноября 2011

Я тестировал класс, который использует слабые ссылки, чтобы убедиться, что объекты можно было собирать мусором, и обнаружил, что объекты в Списке <> никогда не собирались, даже если на список больше не ссылаются. Это также относится и к простому массиву. В следующем фрагменте кода показан простой тест, который не пройден.

class TestDestructor
{
    public static bool DestructorCalled;

    ~TestDestructor()
    {
        DestructorCalled = true;
    }
}

[Test]
public void TestGarbageCollection()
{
    TestDestructor testDestructor = new TestDestructor();

    var array = new object[] { testDestructor };
    array = null;

    testDestructor = null;

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

    Assert.IsTrue(TestDestructor.DestructorCalled);
}

Отсутствие инициализации массива приводит к прохождению теста.

Почему объект в массиве не получает мусор?

Ответы [ 5 ]

4 голосов
/ 11 ноября 2011

РЕДАКТИРОВАТЬ: Хорошо, я делаю некоторые успехи в этом.Возможны три бинарных коммутатора (как минимум):

  • оптимизирован ли код;т. е. флаг /o+ или /o- в командной строке.Кажется, это не имеет значения.
  • Независимо от того, выполняется ли код в отладчике или нет.Кажется, это не имеет значения.
  • Уровень создаваемой отладочной информации, то есть флаг командной строки /debug+, /debug- или /debug:full или /debug:pdbonly.Только /debug+ или /debug:full приводит к сбою.

Дополнительно:

  • Если вы отделите код Main от кода TestDestructor, вы можетескажу, что именно режим компиляции кода Main имеет значение
  • Насколько я могу судить, IL, сгенерированный для /debug:pdbonly, такой же, как для /debug:full в методесамо по себе , так что это может быть явной проблемой ...

РЕДАКТИРОВАТЬ: Хорошо, теперь это действительно странно.Если я разбираю «сломанную» версию, а затем снова собираю ее, она работает:

ildasm /out:broken.il Program.exe
ilasm broken.il

ilasm имеет три различных параметра отладки: /DEBUG, /DEBUG=OPT и /DEBUG=IMPL.При использовании любого из первых двух происходит сбой - при использовании последнего он работает.Последний описан как включающий оптимизацию JIT, так что, по-видимому, в этом и есть разница ... хотя, на мой взгляд, должен по-прежнему иметь возможность собирать объект в любом случае.


Возможно, это связано с моделью памяти в терминах DestructorCalled.Он не является энергозависимым, поэтому нет гарантии, что запись из потока финализатора «видна» вашим тестовым потоком.

Финализаторы, конечно, вызываются в этом сценарии.После создания переменной volatile этот автономный эквивалентный пример (который мне проще запустить) наверняка выведет True для меня.Конечно, это не доказательство: без volatile код не гарантированно потерпит неудачу;это просто не гарантировано работать.Можете ли вы заставить свой тест провалиться после того, как сделает его переменной переменной?

using System;

class TestDestructor
{
    public static volatile bool DestructorCalled;

    ~TestDestructor()
    {
        DestructorCalled = true;
    }
}

class Test
{
    static void Main()
    {
        TestDestructor testDestructor = new TestDestructor();

        var array = new object[] { testDestructor };
        array = null;

        testDestructor = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(TestDestructor.DestructorCalled);
    }
}

РЕДАКТИРОВАТЬ: Я только что видел этот сбой при сборке с Visual Studio, но это было хорошо скомандная строка.Сейчас заглядываю в ИЛ ...

2 голосов
/ 11 ноября 2011

Другое редактирование: результат всегда будет ложным, если массив определен в Main () - Method-Scope, но будет истинным, если определен в Class-Test-Scope. Может быть, это неплохо.

class TestDestructor
{
    public TestDestructor()
    {
        testList = new List<string>();
    }

    public static volatile bool DestructorCalled;

    ~TestDestructor()
    {
        DestructorCalled = true;
    }

    public string xy = "test";

    public List<string> testList;

}

class Test
{
    private static object[] myArray;

    static void Main()
    {
        NewMethod();            
        myArray = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(TestDestructor.DestructorCalled);
        Console.In.ReadToEnd();
    }

    private static void NewMethod()
    {
        TestDestructor testDestructor = new TestDestructor() { xy = "foo" };
        testDestructor.testList.Add("bar");
        myArray = new object[] { testDestructor };
        Console.WriteLine(myArray.Length);
    }
}
1 голос
/ 11 ноября 2011

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

class TestDestructor
{
    public static bool DestructorCalled;
    ~TestDestructor()
    {
        DestructorCalled = true;
    }
}

class Test
{
    static void Main()
    {
        TestDestructor testDestructor = new TestDestructor();

        var array = new object[] { testDestructor };
        Console.WriteLine(array[0].ToString());
        array = null;

        testDestructor = null;

        GC.Collect();
        GC.WaitForPendingFinalizers();
        Console.WriteLine(TestDestructor.DestructorCalled);
    }
} 

Для меня это работает (без летучих) и печатает True всегда. Может кто-нибудь подтвердить, что финализатор не вызывается в режиме выпуска, потому что в противном случае мы можем предположить, что он связан с режимом отладки.

0 голосов
/ 11 ноября 2011

Вот что документация говорит:

Реализация методов Finalize или деструкторов может оказать негативное влияние на производительность, и вы должны избегать их использования без необходимости. Для восстановления памяти, используемой объектами с помощью методов Finalize, требуется как минимум две сборки мусора. [...] Будущая сборка мусора определит, что завершенные объекты действительно являются мусором, потому что на них больше не указывают записи в списке объектов, помеченных как готовые к завершению. В этой будущей сборке мусора память объектов фактически восстанавливается.

Попробуйте использовать механизм удаления вместо завершения, чтобы увидеть, что произойдет

0 голосов
/ 11 ноября 2011

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

Сборщик мусора должен со временем выполнить свою работу, но я понимаю, что вы пытаетесь заставить его очистить ресурсы. Я хотел бы сначала очистить массив (удалить объект), а затем уничтожить его и посмотреть, избавится ли он от всего.

...