Количество загружаемых классов постоянно увеличивается - утечка памяти

У меня есть служба с очень медленной утечкой памяти. Если я проанализирую счетчики .NET CLR Loading, я вижу, что счетчик Текущие классы загружены постоянно увеличивается и всегда соответствует счетчику Всего загруженных классов. У меня создается впечатление, что утечка памяти связана с тем, что ресурсы не освобождаются (это всего лишь предположение).

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

Мне нужно выяснить имена классов, чтобы сузить причину утечки. Я не очень разбираюсь в WinDbg, но мне было интересно, сможет ли кто-нибудь провести меня через шаги, чтобы перечислить эти классы Загружено.

У меня есть исходный код, поэтому при необходимости я могу получить файлы символов. Заранее благодарю за любую помощь!

Вы уничтожаете новый домен приложения каждый раз после его использования?

Jon Skeet 05.12.2008 17:22

Да, вызывается AppDomain.Unload. Кроме того, счетчики производительности для доменов приложений не увеличиваются постоянно.

Page 05.12.2008 18:28
Стоит ли изучать 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 называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
6
2
1 921
9
Перейти к ответу Данный вопрос помечен как решенный

Ответы 9

Я бы посоветовал использовать подходящий профилировщик памяти - например, .NET Memory Profiler - http://memprofiler.com/. Вы, конечно, можете попробовать его при оценке, чтобы увидеть, поможет ли это тот инструмент.

Это позволит вам видеть все живые объекты намного легче, чем хардкорные вещи WinDBG / SoS.

Обновлено: после прочтения некоторых других сообщений их следует принять во внимание после использования лучшего профилировщика и после того, как проблема appDomain будет решена.

Вы можете добавить в свою службу счетчики производительности, чтобы можно было отслеживать хотя бы создаваемые вами объекты. Это поможет вам определить, принадлежат ли вам классы Classes.Loaded или классы CLR.

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

В крайнем случае, вы можете пытаться поместить туда GC.Collect (), чтобы увидеть, исправляет ли его вызов вашу проблему. Это не исправление то, но его тестирование даст вам понять, что это вариант.

Это .net 2.0 или выше? Если это так, возможно, вы не выгружаете AppDomain (как говорит Джон Скит в комментарии).

Если он 1.1 или ниже, в функции выгрузки AppDomain есть ошибка. Т.е. он не освобождает память и ресурсы, когда AppDomain «выгружен».

(Это было исправлено в .net 2.0)

Я согласен, их надо выгружать.

leppie 05.12.2008 17:29

Как сказано в другом ответе, следует использовать AppDomain.Unload ().

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

Посмотрите в документации MSDN для AppDomain.Load (AssemblyName), как это происходит.

Также в той же строке, вы уверены, что используете правильные удаленные классы? Если нет, то обязательно произойдет то, что описано выше.

Домены приложений выгружены, но ответ от leppie заставляет меня задаться вопросом, загружаются ли сборки плагинов как в основной, так и в дополнительный домен приложений. Когда я смотрю на счетчики производительности, текущее количество доменов приложений не увеличивается постоянно.

Приложение должно создать Secondary appDomain, а затем загрузить отдельную сборку плагина. Может быть, какой-нибудь код поможет:

Создание вторичного AppDomain из первичного appDomain:

AppDomainSetup ads = new AppDomainSetup();
ads.ApplicationName = "RemoteAgentLib";
ads.ApplicationBase = AppDomain.CurrentDomain.BaseDirectory;
ads.PrivateBinPath = AppDomain.CurrentDomain.BaseDirectory;
ads.ShadowCopyDirectories = AppDomain.CurrentDomain.BaseDirectory;
ads.ShadowCopyFiles = "true";

m_domain = AppDomain.CreateDomain("RemoteTaskRunner", null, ads);

Используйте RemoteTaskRunner, чтобы загрузить плагин во вторичный appDomain:

RemoteTaskRunner taskRunner = m_domain.CreateInstanceAndUnwrap(
                    Assembly.GetExecutingAssembly().FullName,
                    typeof (RemoteTaskRunner).FullName) as RemoteTaskRunner;
taskRunner.LoadTask(taskInfo.Assembly, taskInfo.Type);

Используйте RemoteTaskRunner для выполнения задачи во вторичном домене приложения:

[Serializable]
internal class RemoteTaskRunner : MarshalByRefObject
{
    private ITask m_task;

    public RemoteTaskRunner()
    {
    }

    internal void LoadTask(string assembly, string type)
    {
        // This assembly should load in the secondary appDomain.
        Assembly taskAssembly = AppDomain.CurrentDomain.Load(assembly);
        m_task = taskAssembly.CreateInstance(type) as ITask;
    }

    internal void RunTask(string taskConfig)
    {
        // This method should run in the secondary appDomain.
        m_task.RunTask(taskConfig, m_logger);
    }
...
...

Для выполнения задачи плагина в основном домене приложения используется следующая строка кода:

taskRunner.RunTask(taskInfo.TaskConfig);

После завершения задачи домен приложения выгружается:

AppDomain.Unload(m_domain);

Вы можете просто выгрузить все сборки, загруженные в основной домен, и посмотреть, загружаются ли они как-то неправильно. Это может быть очень сложно.

leppie 05.12.2008 18:54

Кстати, должен ли ваш MarshalByRefObject быть сериализуемым? Можете ли вы после его создания убедиться, что это действительно прозрачный прокси?

leppie 05.12.2008 18:55

Атрибут Serializable, похоже, не влияет на выполнение. Как с RemoteTaskRunner, так и без него - это прозрачный прокси.

Page 05.12.2008 19:23

Спасибо :) Я не был уверен, что это подействует. Это действительно кажется странной проблемой. Может быть, номер счетчика представляет как загруженный, так и разгруженный тип?

leppie 05.12.2008 19:26

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

Page 05.12.2008 19:29

Я только что прочитал это в блоге Сюзанны Кук.

http://blogs.msdn.com/suzcook/archive/2003/06/12/57169.aspx

Be sure to not pass any Type/Assembly/etc. instances (besides your MarshalByRefObject type) back to the original appdomain. If you do, it will cause those assemblies to be loaded into the original appdomain. If the appdomain settings are different between the two appdomains, those assemblies may not be loadable there. Plus, even if they are successfully loaded, the assemblies will remain loaded and locked after the target appdomain is unloaded, even if the original appdomain never uses them.

Когда она говорит какой-нибудь Тип / Сборка / и т. д. Что она может ввести ЛЮБОЙ тип? Причина, по которой я спрашиваю, заключается в том, что мой MarshalByRefObject (RemoteTaskRunner) действительно возвращает объект DateTime после выполнения задачи. Может ли это привести к загрузке сборки плагина в мой основной домен приложения (и, в конечном итоге, вызвать утечку памяти)?

Нет, DateTime подойдет, поскольку сборка mscorlib уже загружена.

leppie 05.12.2008 19:16

Вы всегда можете проверить, какие сборки загружены в ваш AppDomain:

foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies())
{
      Console.WriteLine(assembly.FullName);
}

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

Редактировать:

если вы хотите использовать WinDgb SOS, поддерживаемые команды - здесь. Скорее всего, вас интересуют: DumpDomain, DumpClass, DumpAssembly, EEHeap ...

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

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

Похоже, это не первый раз, когда FileSystemWatchers вызывает у меня проблемы. :)

Спасибо всем (особенно leppie) за то, что помогли мне с этим!

Я всегда выбрасываю это, когда кто-нибудь сообщает об утечке памяти, потому что это занимало меня пару недель. Не запускайте приложение в режиме отладки. Если вы запускаете свое приложение в режиме отладки в .Net 2.0+ (этого не было в .Net 1.1), и вы создаете экземпляр класса, который содержит событие, даже если вы не инициируете событие, он будет содержать только небольшой кусок памяти. Это может сильно повлиять на долго работающие приложения, такие как службы и веб-приложения, потому что со временем небольшой объем памяти, израсходованный после создания экземпляров объектов, может составить довольно много.

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