Циркулярные ссылки вызывают утечку памяти?

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

Родительская форма имеет ссылку на дочернюю форму через коллекцию элементов управления (дочерняя форма встроена в нее). Дочерняя форма не GC'd.

Дочерняя форма имеет ссылку на родительскую форму через поле закрытого члена. Родительская форма не хранится в GC.

Это точное понимание того, как сборщик мусора оценит ситуацию? Есть ли способ «доказать» это для целей тестирования?

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
38
0
14 677
6
Перейти к ответу Данный вопрос помечен как решенный

Ответы 6

Ответ принят как подходящий

Отличный вопрос!

Нет, обе формы будут (могут быть) GC'd, потому что GC напрямую не ищет ссылки в других ссылках. Он ищет только так называемые "корневые" ссылки ... Сюда входят ссылочные переменные в стеке (переменная находится в стеке, фактический объект, конечно, находится в куче), ссылочные переменные в регистрах ЦП и ссылочные переменные, которые являются статические поля в классах ...

Все другие ссылочные переменные доступны (и GC'd) только в том случае, если на них есть ссылка в свойстве одного из «корневых» ссылочных объектов, найденных вышеупомянутым процессом ... (или в объекте, на который ссылается ссылка в корневом объекте. , так далее...)

Таким образом, только если одна из форм упоминается где-то еще в «корневой» ссылке - тогда обе формы будут защищены от GC.

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

Или просто выделите большой буфер внутри каждой формы.

Gusdor 06.02.2012 16:22

Если и родитель, и потомок не упоминаются, но ссылаются только друг на друга, они получают GCed.

Получите профилировщик памяти, чтобы действительно проверить свое приложение и ответить на все ваши вопросы. Могу порекомендовать http://memprofiler.com/

Сборщик мусора может правильно обрабатывать циклические ссылки, и если бы эти ссылки были единственными вещами, поддерживающими форму, они были бы собраны. У меня было много проблем с .net, не восстанавливающим память из форм. В 1.1 были некоторые ошибки в элементах меню (я думаю), что означало, что они не удалялись и могли вызывать утечку памяти. В этом случае добавление явного вызова для удаления и очистки переменной-члена в методе Dispose формы решило проблему. Мы обнаружили, что это также помогает освободить память для некоторых других типов элементов управления. Я также провел много времени с профилировщиком CLR, пытаясь понять, почему не собираются формы. Насколько я мог судить, ссылки сохранялись во фреймворке. По одному на каждый тип формы. Итак, если вы создадите 100 экземпляров Form1, а затем закроете их все, только 99 будут восстановлены должным образом. Я не нашел способа вылечить это. С тех пор наше приложение переместилось на .net 2, и это кажется намного лучше. Память нашего приложения по-прежнему увеличивается, когда мы открываем первую форму, и не возвращается обратно, когда она закрывается, но я считаю, что это из-за загруженного кода JIT и дополнительных библиотек управления. Я также обнаружил, что хотя сборщик мусора может работать с циклическими ссылками, у него (иногда) возникают проблемы с циклическими ссылками на обработчики событий. IE object1 ссылается на object2, а object1 имеет метод, который обрабатывает и обрабатывает события от object2. Я обнаружил обстоятельства, при которых объекты не высвобождались, когда я ожидал, но мне так и не удалось воспроизвести их в тестовом примере.

Как уже говорили другие, у GC нет проблем с циклическими ссылками. Я просто хотел бы добавить, что обычным местом утечки памяти в .NET являются обработчики событий. Если одна из ваших форм имеет прикрепленный обработчик событий к другому объекту, который является «живым», то есть ссылка на вашу форму, и форма не получит GC'd.

Сборка мусора работает путем отслеживания корней приложений. Корни приложений - это места хранения, которые содержат ссылки на объекты в управляемой куче (или на null). В .NET корни

  1. Ссылки на глобальные объекты
  2. Ссылки на статические объекты
  3. Ссылки на статические поля
  4. Ссылки в стеке на локальные объекты
  5. Ссылки в стеке на параметры объекта, передаваемые в методы
  6. Ссылки на объекты, ожидающие завершения
  7. Ссылки в регистрах ЦП на объекты в управляемой куче

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

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

@ Джейсон, что вы имеете в виду под "параметром объекта"? И я считаю, что расположение ссылки является решающим фактором ... Если в стеке, или в статическом члене класса, или в регистре ЦП, то это корневая ссылка. ... иначе нет. (кроме свободной очереди, - отдельная тема)

Charles Bretana 30.12.2008 20:12

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

Допустим, у вас есть тип, являющийся источником события, например:

interface IEventSource
{
    event EventHandler SomethingHappened;
}

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

Что поднимает вопрос об утилизации. Любой класс, который подписывается на события, должен реализовывать интерфейс IDisposable, поскольку события являются управляемыми ресурсами. (N.B. Я пропустил правильную реализацию шаблона Dispose в примере для краткости, но вы поняли идею.)

class MyClass : IDisposable
{
    IEventSource m_EventSource;
    public IEventSource EventSource
    {
        get { return m_EventSource; }
        set
        {
            if ( null != m_EventSource )
            {
                m_EventSource -= HandleSomethingHappened;
            }
            m_EventSource = value;
            if ( null != m_EventSource )
            {
                m_EventSource += HandleSomethingHappened;
            }
        }
    }

    public Dispose()
    {
        EventSource = null;
    }

    // ...
}

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