Отладка утечки памяти C#

Я обнаружил утечку памяти в своей программе и после долгих исследований смог изолировать проблему и создал этот образец:

Целевая платформа: .NET Framework 4.7.2

AppFormMain.cs:

private void MainForm_Shown(object sender, EventArgs e)
{
    for (int i = 0; i < 90000; i++)
    {
        Console.WriteLine("i " + i);
        AppMain.GetInstance().memoryLeakTest();
    }
    GC.Collect();
    GC.WaitForPendingFinalizers(); GC.Collect(); // ADDED ON [UPDATE 2]
}

AppMain.cs:

protected static AppMain appInstance;

public AppMain()
{
    SetInstance(this);
}

public void SetInstance(AppMain value)
{
    appInstance = value;
}

public static AppMain GetInstance()
{
    return appInstance;
}

public void memoryLeakTest()
{
    ToolStripMenuItem item = new ToolStripMenuItem();
    item.Text = "Create";
    item.Image = System.Resources.create; // png 16x16 47.6KB
    item.Dispose(); // ADDED ON [UPDATE 1]
}

Если я удаляю цикл в MainForm_Shown (), я получаю примерно 93 МБ ram. Если я зацикливаю 90000 раз, максимальный ram составляет 1,1 ГБ во время цикла, а после сборщика мусора я заканчиваю использование 180 МБ, и после этого он не снижается.

Если я удалю строку «item.Image = ...», использование будет менее агрессивным, но у меня все еще будет утечка памяти около 150 МБ.

Почему эта утечка памяти? переменная в memoryLeakTest () даже не используется ....

Цикл 90000 предназначен только для тестирования, представляющего многочасовое использование. При использовании приложения через 24 часа оно использует 2 ГБ оперативной памяти только потому, что требуется обновление в пользовательском интерфейсе, при загрузке используется только 100 МБ.

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

Извините, если я забываю важную информацию, это моя первая программа на C#.

Спасибо.

[ОБНОВЛЕНИЕ 1] Добавлен Dispose(), как было предложено в комментариях, помог, но не полностью решил проблему. Значения использования памяти в приведенном выше тексте были обновлены с учетом новых результатов.

[ОБНОВЛЕНИЕ 2] Добавлен GC.WaitForPendingFinalizers(); GC.Collect();, как предлагалось в комментариях. Это помогло уменьшить со 180 МБ до 130 МБ. По-прежнему остается разница в ~ 40 МБ по сравнению с базовым уровнем ~ 90 МБ без цикла.

Вы уверены, что это утечка? То, что сборщик мусора еще не собрал его, не означает, что он просочился. Все неправильно думают о сборке мусора

madreflection 30.03.2021 21:13

Следует избавиться от пункта меню: Методы ToolStripMenuItem

Silvermind 30.03.2021 21:14
int i = 0; i < 90000 - это много ToolStripMenuItem, чего вы пытаетесь достичь?
Codexer 30.03.2021 21:23

Пытаюсь добавить GC.Collect (); после for () и ничего не сделал. Я также добавил item.Dispose (); в конце memoryLeakTest (), и это снизило использование памяти примерно до 180 МБ. 90000 - это действительно много объектов, я пытаюсь воспроизвести проблему использования оперативной памяти 2 ГБ после 24 часов использования только потому, что мне нужно перестроить пользовательский интерфейс приложения ...

Seltix 30.03.2021 21:36
Что-нибудь и все, которые включают метод Dispose, должны быть удалены, когда вы закончите с ним. В зависимости от того, что на самом деле делает Resources.create, у вас может быть еще одна утечка.
Ňɏssa Pøngjǣrdenlarp 30.03.2021 21:42

хорошо, Dispose помог, но не устранил проблему полностью. Файл Resources.created - это просто png 16x16 47.6KB из ресурсов проекта.

Seltix 30.03.2021 21:59

Я обновил исходный пост всей этой информацией

Seltix 30.03.2021 22:05

Попробуйте GC.Collect(); GC.WaitForPendingFinalizers(); GC.Collect();

Lasse V. Karlsen 30.03.2021 22:32

Это помогло, уменьшилось со 180 МБ до 130 МБ. Еще есть разница в ~ 40мб. Я не понимаю, что голосование "против", это так просто решить ??

Seltix 30.03.2021 22:45

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

Charlieface 30.03.2021 23:50
ToolStripMenuItem.Dispose() не удаляет установленный вами образ. Он не может этого сделать: это может быть общий ресурс, который используют другие элементы управления. Но поскольку вы создаете этот образ с помощью Factory, скорее всего, вам также потребуется вызвать item.Image?.Dispose(). Когда вы используете общий Bitmap, вам нужно избавиться только от него, когда класс, который его создал, удаляется (или, с Form, когда он закрывается).
Jimi 31.03.2021 01:46

привет, образ - это просто ресурс из приложения в Resources > images (build action: resource). item.Image?.Dispose() ничем не отличается: S

Seltix 31.03.2021 04:06

Только что протестировал вашу настройку, заменив public static AppMain GetInstance() { if (appInstance == null) { appInstance = new AppMain(); } return appInstance; }. В memoryLeakTest(), как уже упоминалось, добавлено: item.Image?.Dispose(); item.Dispose(); (для item.Image задан объект ресурса). Тогда цикл становится: var inst = AppMain.GetInstance(); for(...) { inst.memoryLeakTest(); } inst = null;.

Jimi 31.03.2021 10:05

Конечно, есть начальное выделение памяти из-за создания объектов. После этого инструмент диагностики покажет использование памяти постоянный во время выполнения цикла и общий прирост памяти в 4,2 МБ, который не запускает сборку мусора, так как это не имеет значения (он также может быть собран позже или никогда, учитывая малый объем используемой памяти). GC.Collect(), конечно, вообще не действует, так как все расположено идеально, как и ожидалось.

Jimi 31.03.2021 10:08

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

Seltix 31.03.2021 12:46

привет, я создал этот образец программы (это просто Program.cs) paste.in/7J9mtx. Это не совсем то же самое, но почти. Вы можете запустить его? Из моих тестов Если удалить asyncCall (); или второй while () У меня нет утечки памяти, знаете почему? можете ли вы воспроизвести те же результаты?

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

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