Странное поведение при модульном тестировании утечки памяти с использованием WeakReference

Я пытаюсь написать модульный тест для проверки утечки памяти. Действия по воспроизведению:

    TestClass test = new TestClass();
    WeakReference reference = new WeakReference(test, true);

    test = null;

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

    Debug.Assert(reference.Target == null, "Memory Leak"); // This works

     //Replacing the above line with following, I see "Memory Leak" being printed.

    if (reference.Target != null)
    {
        Console.WriteLine("Memory Leak");
    }

Я добавил финализатор:

~TestClass() { Console.WriteLine("TestClass instance finalized");}

и я заметил, что финализатор вызывается как часть команд GC в случае Assert, но когда я заменяю его условием if, финализатор не вызывается как часть команд GC, и, следовательно, цель ссылки все еще жива. Финализатор вызывается только до того, как программа существует.

Ожидаемое поведение:

if(reference.Target != null) Console.WriteLine("Memory Leak");

должно сработать.

Фактическое поведение:

Debug.Assert(reference.Target == null, "Memory Leak");

работает, но

if(reference.Target != null) Console.WriteLine("Memory Leak");

не работает, так как печатает "Memory Leak"


person Rajesh Nagpal    schedule 19.02.2017    source источник


Ответы (1)


Я нашел основную причину этой проблемы. Этот код будет работать, как и ожидалось, в конфигурации сборки Release, но не в режиме отладки (это то, что я запускал).

В случае отладки причина, по которой «тест» не проходит GCed, заключается в том, что существует «ссылка» члена, у которого есть свойство «Цель», которое содержит ссылку на объект «тест». Чтобы иметь возможность просмотреть это с помощью инструментов отладчика, таких как окно Watch, компилятор поддерживает его в активном состоянии. Если вы избавитесь от экземпляра WeakReference (и соответствующего условия if), вы увидите, что он подвергается сборке мусора даже в режиме отладки. Кроме того, кажется, что если «ссылка» используется в Debug.Assert, она не содержит ссылку на цель и позволяет «тестировать» для GC.

В режиме Release причина, по которой «тест» является сборщиком мусора, заключается в том, что компилятор JIT компилирует код и избавляется от «тестовой» переменной (поскольку ей в любом случае присваивается значение null), и нет возможности сослаться на нее где-либо в код. Это позволяет GC'ed. Поскольку «ссылка» является слабой ссылкой на «тестовый» объект, она не будет хранить его и позволит выполнить GC, и, следовательно, условие if работает в режиме Release.

person Rajesh Nagpal    schedule 21.02.2017