Поиск…


Вступление

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

Некоторые из приведенных ниже примеров - это «лабораторные случаи», показывающие сборщик мусора на работе и некоторые существенные детали его поведения, в то время как другие сосредоточены на том, как подготовить классы для правильной обработки Сборщиком мусора.

замечания

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

  • Если метод Collect () должен быть явно вызван (что не так часто бывает так), рассмотрите использование «оптимизированного» режима, который завершает работу с мертвым объектом только тогда, когда на самом деле нужна память
  • Вместо вызова метода Collect () используйте методы AddMemoryPressure () и RemoveMemoryPressure (), которые запускают коллекцию памяти только в случае необходимости
  • Сбор памяти не гарантируется, чтобы завершить все мертвые объекты; вместо этого сборщик мусора управляет 3 «поколениями», объект иногда «выживает» от поколения к следующему
  • В зависимости от различных факторов, включая настройку тонкой настройки, могут применяться несколько моделей потоков, что приводит к разным степеням помех между резьбой коллектора мусора и другими прикладными нитями (нитями)

Основной пример сбора (мусора)

Учитывая следующий класс:

public class FinalizableObject 
{
    public FinalizableObject()
    {
        Console.WriteLine("Instance initialized");
    }

    ~FinalizableObject()
    {
        Console.WriteLine("Instance finalized");
    }
}

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

new FinalizableObject(); // Object instantiated, ready to be used

Производит следующий вывод:

<namespace>.FinalizableObject initialized

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

Можно заставить сборщик мусора работать в данной точке, как показано ниже:

new FinalizableObject(); // Object instantiated, ready to be used
GC.Collect();

Что дает следующий результат:

<namespace>.FinalizableObject initialized
<namespace>.FinalizableObject finalized

На этот раз, как только был вызван сборщик мусора, неиспользуемый (он же «мертвый») объект был доработан и освобожден от управляемой кучи.

Живые объекты и мертвые объекты - основы

Правило: когда происходит сбор мусора, «живые объекты» - это те, которые все еще используются, тогда как «мертвые объекты» - это те, которые больше не используются (любая переменная или поле, ссылающееся на них, если таковые имеются, вышло за рамки до того, как произойдет сбор) ,

В следующем примере (для удобства FinalizableObject1 и FinalizableObject2 являются подклассами FinalizableObject из приведенного выше примера и, таким образом, наследуют поведение сообщения инициализации / завершения):

var obj1 = new FinalizableObject1(); // Finalizable1 instance allocated here
var obj2 = new FinalizableObject2(); // Finalizable2 instance allocated here
obj1 = null; // No more references to the Finalizable1 instance 
GC.Collect();

Выход будет:

<namespace>.FinalizableObject1 initialized
<namespace>.FinalizableObject2 initialized
<namespace>.FinalizableObject1 finalized

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

Несколько мертвых объектов

Что, если два (или несколько) иначе мертвых объекта ссылаются друг на друга? Это показано в примере ниже, если предположить, что OtherObject является общедоступным свойством FinalizableObject:

var obj1 = new FinalizableObject1(); 
var obj2 = new FinalizableObject2();
obj1.OtherObject = obj2;
obj2.OtherObject = obj1;
obj1 = null; // Program no longer references Finalizable1 instance
obj2 = null; // Program no longer references Finalizable2 instance
// But the two objects still reference each other
GC.Collect();

Это дает следующий результат:

<namespace>.FinalizedObject1 initialized
<namespace>.FinalizedObject2 initialized
<namespace>.FinalizedObject1 finalized
<namespace>.FinalizedObject2 finalized

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

Слабые ссылки

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

Следующий код:

var weak = new WeakReference<FinalizableObject>(new FinalizableObject());
GC.Collect();

Производит вывод:

<namespace>.FinalizableObject initialized
<namespace>.FinalizableObject finalized

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

Следствие №1: в любое время небезопасно предполагать, что цель WeakReference по-прежнему распределяется по управляемой куче или нет.

Следствие №2: всякий раз, когда программе необходимо получить доступ к объекту Weakreference, код должен быть предоставлен для обоих случаев, а цель еще выделена или нет. Метод доступа к целевому объекту - TryGetTarget:

var target = new object(); // Any object will do as target
var weak = new WeakReference<object>(target); // Create weak reference
target = null; // Drop strong reference to the target

// ... Many things may happen in-between

// Check whether the target is still available
if(weak.TryGetTarget(out target))
{
    // Use re-initialized target variable
    // To do whatever the target is needed for
}
else
{
    // Do something when there is no more target object
    // The target variable value should not be used here
}

Общая версия WeakReference доступна с .Net 4.5. Все версии фреймворка предоставляют нестандартную, нетипизированную версию, которая построена таким же образом и проверяется следующим образом:

var target = new object(); // Any object will do as target
var weak = new WeakReference(target); // Create weak reference
target = null; // Drop strong reference to the target

// ... Many things may happen in-between

// Check whether the target is still available
if (weak.IsAlive)
{
    target = weak.Target;

    // Use re-initialized target variable
    // To do whatever the target is needed for
}
else
{
    // Do something when there is no more target object
    // The target variable value should not be used here
}

Утилизировать () против финализаторов

Внедрите метод Dispose () (и объявите содержащийся класс как IDisposable) в качестве средства обеспечения освобождения ресурсов памяти, как только объект больше не будет использоваться. «Уловка» заключается в том, что нет надежной гарантии того, что метод Dispose () будет когда-либо вызываться (в отличие от финализаторов, которые всегда вызываются в конце жизни объекта).

Один из сценариев - это программа, вызывающая Dispose () для объектов, которые она явно создает:

private void SomeFunction()
{
    // Initialize an object that uses heavy external resources
    var disposableObject = new ClassThatImplementsIDisposable();

    // ... Use that object

    // Dispose as soon as no longer used
    disposableObject.Dispose();

    // ... Do other stuff 

    // The disposableObject variable gets out of scope here
    // The object will be finalized later on (no guarantee when)
    // But it no longer holds to the heavy external resource after it was disposed
}

Другой сценарий - объявить класс, который будет создан в рамках структуры. В этом случае новый класс обычно наследует базовый класс, например, в MVC создается класс контроллера в качестве подкласса System.Web.Mvc.ControllerBase. Когда базовый класс реализует интерфейс IDisposable, это хороший намек на то, что утилита Dispose () будет правильно использована инфраструктурой, но опять же нет сильной гарантии.

Таким образом Dispose () не является заменой финализатора; вместо этого они должны использоваться для разных целей:

  • Финализатор в конечном итоге освобождает ресурсы, чтобы избежать утечек памяти, которые могли бы произойти иначе
  • Dispose () освобождает ресурсы (возможно, те же самые), как только они больше не нужны, чтобы уменьшить давление на общее распределение памяти.

Надлежащее удаление и завершение объектов

Поскольку Dispose () и финализаторы нацелены на разные цели, класс, управляющий ресурсами с большой памятью, должен реализовать оба из них. Следствием является запись класса, чтобы он хорошо справлялся с двумя возможными сценариями:

  • Когда вызывается только финализатор
  • Когда Dispose () вызывается сначала и позже, вызывается также финализатор

Одно из решений записывает код очистки таким образом, что запуск его один или два раза приведет к тому же результату, что и его запуск только один раз. Технико-экономическое обоснование зависит от характера очистки, например:

  • Закрытие уже закрытого соединения с базой данных, вероятно, не будет иметь никакого эффекта, поэтому оно будет работать
  • Обновление некоторого «количества использования» опасно и приведет к неправильному результату при вызове дважды, а не в один раз.

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

public class DisposableFinalizable1: IDisposable
{
    private bool disposed = false;

    ~DisposableFinalizable1() { Cleanup(); }

    public void Dispose() { Cleanup(); }

    private void Cleanup()
    {
        if(!disposed)
        {
            // Actual code to release resources gets here, then
            disposed = true;
        }
    }
}

В качестве альтернативы сборщик мусора предоставляет специальный метод SuppressFinalize (), который позволяет пропустить финализатор после вызова Dispose:

public class DisposableFinalizable2 : IDisposable
{
    ~DisposableFinalizable2() { Cleanup(); }

    public void Dispose()
    {
        Cleanup();
        GC.SuppressFinalize(this);
    }

    private void Cleanup()
    {
        // Actual code to release resources gets here
    }
}


Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow