У меня есть служба с очень медленной утечкой памяти. Если я проанализирую счетчики .NET CLR Loading, я вижу, что счетчик Текущие классы загружены постоянно увеличивается и всегда соответствует счетчику Всего загруженных классов. У меня создается впечатление, что утечка памяти связана с тем, что ресурсы не освобождаются (это всего лишь предположение).
Служба создает новые домены приложений каждый раз, когда выполняет задачу (подключаемая архитектура).
Мне нужно выяснить имена классов, чтобы сузить причину утечки. Я не очень разбираюсь в WinDbg, но мне было интересно, сможет ли кто-нибудь провести меня через шаги, чтобы перечислить эти классы Загружено.
У меня есть исходный код, поэтому при необходимости я могу получить файлы символов. Заранее благодарю за любую помощь!
Да, вызывается AppDomain.Unload. Кроме того, счетчики производительности для доменов приложений не увеличиваются постоянно.





Я бы посоветовал использовать подходящий профилировщик памяти - например, .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)
Я согласен, их надо выгружать.
Как сказано в другом ответе, следует использовать 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);
Вы можете просто выгрузить все сборки, загруженные в основной домен, и посмотреть, загружаются ли они как-то неправильно. Это может быть очень сложно.
Кстати, должен ли ваш MarshalByRefObject быть сериализуемым? Можете ли вы после его создания убедиться, что это действительно прозрачный прокси?
Атрибут Serializable, похоже, не влияет на выполнение. Как с RemoteTaskRunner, так и без него - это прозрачный прокси.
Спасибо :) Я не был уверен, что это подействует. Это действительно кажется странной проблемой. Может быть, номер счетчика представляет как загруженный, так и разгруженный тип?
Я добавил диагностический код, чтобы записать, сколько сборок было загружено в основной домен приложения. Похоже, что никакого увеличения не происходит. Так, может быть, проблема не в доменах приложений?
Я только что прочитал это в блоге Сюзанны Кук.
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 уже загружена.
Вы всегда можете проверить, какие сборки загружены в ваш 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), и вы создаете экземпляр класса, который содержит событие, даже если вы не инициируете событие, он будет содержать только небольшой кусок памяти. Это может сильно повлиять на долго работающие приложения, такие как службы и веб-приложения, потому что со временем небольшой объем памяти, израсходованный после создания экземпляров объектов, может составить довольно много.
Вы уничтожаете новый домен приложения каждый раз после его использования?