Возможно ли иметь утечку памяти в управляемом коде? (в частности, C# 3.0)

Например, если у меня есть иерархическая структура данных:

class Node
{
    public List<Node> children;
}

и оно заселено на много уровней вниз, тогда в одном из родителей идут:

myNode.children.Clear();

который очистит все ссылки на непосредственных детей - но как насчет всех внуков, внуков и т. д., на которые ссылались эти непосредственные дети? Достаточно ли умен C#, чтобы знать, что они больше не нужны, и что они будут собирать мусор?

Я прочитал с использованием привязки данных WPF без реализации интерфейса INotifyChanged может вызвать утечки памяти: http://blogs.msdn.com/b/micmcd/archive/2008/03/07/avoiding-a-wpf-memory-leak-with-databinding-black-magic.aspx, как это возможно в управляемой среде?

11 ответов

Решение

Да, сборщик мусора определит, что внуки и т.д. - мусор. По сути, если нет возможности добраться до объекта, это считается мусором и может быть собрано.

Что касается того, как "утечки" памяти возможны в управляемом коде - это обычно, если вы в конечном итоге получаете объект, доступный через ссылки на объекты, но где нет никакого способа, которым вы можете в конечном итоге "очистить" эти ссылки через API.

Это тот случай, когда вы цитируете сообщение в блоге:

Существует проблема, когда WPF проверяет, чтобы найти вещи, которые реализуют INotifyProperyChanged. Если существует привязка данных к чему-то, что не реализует этот интерфейс, то он делает запись в глобальной таблице. Эта запись не очищается, так как WPF не может проверить, когда эта запись в БД больше не нужна.

Итак, эта глобальная таблица содержит ссылки, и вы не можете указать, что элемент в таблице может быть очищен.

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

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

Пример:

class Foo {
 public Node SomeProperty {get; set;}

    public void SomeFunction(){
        var node = new Node { children = new List<Node>() };
        var childNode = new Node();
        var childNode2 = new Node();
        node.children.Add(childNode);
        node.children.Add(childNode2);
        SomeProperty = childNode2;

        node.children.Clear();
        // childNode will be garbage collected
        // childNode2 is still used by SomeProperty,
        // so it won't be garbage collected until SomeProperty or the instance
        // of Foo is no longer used.
    }
}

C# не волнует. Это работа CLR, чтобы сделать GC.

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

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


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

Например, при использовании базы данных GUI имеет ссылки на объекты, поддерживающие их работу.

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

Кроме того, вы можете также получить утечки памяти в.net, если вы используете ключевое слово unsafe. Если вы используете указатели таким же образом, как C++ и т. Д., И не заботитесь о том, чтобы не "потерять" ссылку на указатель, то сборщик мусора не сможет ее собрать.

пример небезопасного блока;

unsafe
{
int * ptr1, ptr2;
ptr1 = &var1;
ptr2 = ptr1;
*ptr2 = 20;
}

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

Вот пример из класса Image:

public static void MemLeak()
{
    var src = @"C:\users\devshorts\desktop\bigImage.jpg";

    Image image1 = null;

    foreach (var i in Enumerable.Range(0, 10))
    {
        image1 = Image.FromFile(src);
    }

    image1.Dispose();

    Console.ReadLine();
}

Image одноразовый, так что, так как в конце я избавляюсь от изображения, не должно быть утечки, верно? Фактически тот факт, что вы каждый раз перезаписываете ссылку новым изображением, означает, что вы не можете избавиться от базовых ресурсов GDI+, которые содержала старая ссылка на изображение. Это приведет к утечке памяти.

Так как gc не вызывает распоряжение для вас и Image класс не отменяет Finalize метод (и вызовите Dispose там), тогда у вас есть утечка.

Утечка памяти - это, по сути, часть памяти, которая больше не требуется для правильного поведения программы, но не может быть освобождена из-за ошибки программирования. Таким образом, концепция утечек памяти не имеет ничего общего с сборкой мусора, C# или Java.

Возьмите этот пример:

var list = new List<Node>();
Node a1 = new Node();
Node a2 = new Node();
// ...
Node an = new Node();

// Populate list
list.Add(a1);
list.Add(a2);
// ...
list.Add(an);

// use this list
DoStuffTo(list);

// clear list -- release all elements
list.Clear();

// memory leaks from now on

Обратите внимание, что элементы в списке являются утечками памяти, потому что на них ссылаются переменные a1 ... an

Это всего лишь простой пример того, почему не только C# решает проблему утечек памяти. Разработчик также обязан исправить это:

// Clear references
a1 = null;
a2 = null;
// ...
an = null;

Это скажет сборщику мусора C#, что все эти элементы должны быть собраны.

Да, утечки в C# возникают, когда ссылки на объекты не удаляются должным образом, когда эти объекты больше не нужны. Если ссылка на объект была удалена, то этот объект удаляется сборщиком мусора при его запуске (он делает это автоматически в зависимости от времени, определенного тщательно настроенным алгоритмом, поэтому лучше не запускать его вручную, если только Вы действительно знаете, что делаете!). Но если ссылка на объект не удалена должным образом, сборщик мусора все еще думает, что это необходимо приложению, поэтому утечка памяти. Такое часто встречается в обработчиках событий, от которых должным образом не избавляются. Если для объекта с детьми / внуками удалены все ссылки на него, то этот объект, а также все эти дети / внуки также будут удалены при следующем запуске сборщика мусора (если на них не ссылаются из других источников).

Лучше всего использовать профилировщик памяти, который позволит вам посмотреть, какие объекты содержат другие объекты в памяти (большинство позволяет делать снимки памяти, а затем смотреть на какой-то график, показывающий ссылки. Если объект все еще существует, когда он не следует, вы можете посмотреть на график, показывающий, какая ссылка удерживает этот объект в памяти, и использовать ее, чтобы выяснить, где вы должны были очистить ссылку, чтобы избежать утечки памяти. Доступно несколько профилировщиков, но я нахожу память муравьев Профилировщик by red gate проще всего использовать http://www.red-gate.com/products/dotnet-development/ants-memory-profiler/.

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

Если это не так, либо GC не запустился (вы можете попробовать GC.Collect() для диагностических целей, но вы не должны использовать его в рабочем коде), или что-то относится к части структуры. Например, пользовательский интерфейс может быть привязан к нему.

Возможна утечка памяти в.NET.

Если у вас есть объект "A", который регистрируется в событии другого объекта "B", то "B" получает ссылку на "A" и будет продолжать иметь его, если вы не отмените регистрацию события, когда "A" выйдет области видимости В этом случае "А" не может быть собран мусором, так как есть еще активная ссылка. Он будет держаться до тех пор, пока "B" не соберет мусор.

Если у вас есть ситуация, когда объекты "А" создаются и постоянно выходят из области видимости, вы будете получать все больше и больше "А" в памяти.

Я бы посоветовал прочитать о том, как сборка мусора обрабатывается в мире.net - по сути, он работает, следуя ссылкам, чтобы найти все, на что может ссылаться объект верхнего уровня, и освобождает все остальное; он не работает с деструкторами, такими как мир C++, поэтому вы можете быть счастливы, зная, что управляемые объекты "просто уйдут", если их родительский (ые) и прародительский (ые) родители будут освобождены.

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

Сложный бит возникает при работе с тем, что может ссылаться на объект, и он включает в себя некоторые менее очевидные вещи, такие как обработчики событий, из которых и вытекает упомянутая вами проблема WPF/INotifyPropertyChanged.

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

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

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

Другие вопросы по тегам