Текущие классы постоянно загружаются - утечка памяти - PullRequest
6 голосов
/ 05 декабря 2008

У меня есть служба с очень медленной утечкой памяти. Если я проанализирую счетчики загрузки .NET CLR, то увижу, что счетчик Current Classes Loaded постоянно увеличивается и постоянно соответствует счетчику Total Classes Loaded . Это создает у меня впечатление, что утечка памяти связана с не освобождающимися ресурсами (это только предположение).

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

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

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

Ответы [ 9 ]

2 голосов
/ 05 декабря 2008

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

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

Спасибо всем (особенно леппи) за помощь в этом!

2 голосов
/ 05 декабря 2008

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

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

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

2 голосов
/ 05 декабря 2008

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

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

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

Edit:

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

1 голос
/ 05 декабря 2008

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

1 голос
/ 05 декабря 2008

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

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

Обязательно не пропустите Тип / Assembly / и т.д.. случаи (кроме ваш тип MarshalByRefObject) вернуться к оригинальный домен приложения. Если вы делаете, это приведет к тому, что эти сборки будут загружен в исходный домен приложения. Если настройки appdomain разные между двумя доменами приложений, те сборки не могут быть загружены там. Плюс, даже если они успешно загружен, сборки останутся загружен и заблокирован после цели appdomain выгружается, даже если оригинальный домен никогда не использует их.

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

1 голос
/ 05 декабря 2008

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

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

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

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

1 голос
/ 05 декабря 2008

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

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

0 голосов
/ 05 декабря 2008

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

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

Создание вторичного домена приложений из первичного домена приложений:

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 для загрузки плагина во вторичном домене приложения:

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);
0 голосов
/ 05 декабря 2008

РЕДАКТИРОВАТЬ: После прочтения некоторых других сообщений, они должны быть приняты во внимание после использования лучшего профилировщика и после решения проблемы appDomain.

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

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

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

...