Подтвердить что ты не робот

.NET Проблемы с памятью загружаются ~ 40 изображений, память не регенерирована, потенциально из-за фрагментации LOH

Ну, это мой первый набег на профилирование .NET-приложения (настройка процессора, которую я сделал), и я нахожу здесь немного стены.

У меня есть представление в моем приложении, которое загружает 40 изображений (макс.) на страницу, каждый из которых работает около ~ 3 МБ. Максимальное количество страниц - 10. Видя, что я не хочу сохранять 400 изображений или 1,2 ГБ в памяти сразу, я устанавливаю каждое изображение равным нулю при изменении страницы.

Теперь, сначала я подумал, что у меня должны быть просто устаревшие ссылки на эти образы. Я загрузил профилировщик ANTS (отличный инструмент BTW) и провел несколько тестов. График жизни объекта говорит мне, что у меня нет ссылок на эти изображения, отличные от одиночной ссылки в родительском классе (который по дизайну, также подтвержденный тщательным расчетом через мой код):

enter image description here

Родительский класс SlideViewModelBase всегда хранится в кеше, но свойство MacroImage имеет значение null, когда страница изменена. Я не вижу никаких признаков того, что эти объекты должны храниться дольше, чем ожидалось.

Далее я рассмотрел кучу больших объектов и использование памяти в целом. После просмотра трех страниц изображений у меня есть 691.9 МБ неуправляемой памяти и 442.3 МБ на LOH. System.Byte[], который поступает из моего преобразования System.Drawing.Bitmap в BitmapImage, занимает почти все пространство LOH. Вот мой код конверсии:

public static BitmapSource ToBmpSrc( this Bitmap b )
{
    var bi = new BitmapImage();
    var ms = new MemoryStream();
    bi.CacheOption = BitmapCacheOption.OnLoad;
    b.Save( ms,  ImageFormat.Bmp );
    ms.Position = 0;
    bi.BeginInit();
    ms.Seek( 0, SeekOrigin.Begin );
    bi.StreamSource = ms;
    bi.EndInit();
    return bi;
}

Мне трудно найти, куда идет вся эта неуправляемая память. Сначала я подозревал объекты System.Drawing.Bitmap, но ANTS не показывал, что они торчат, и я также провел тест, где я абсолютно уверен, что все они были удалены, и это не изменило ситуацию. Поэтому я еще не понял, откуда происходит вся неуправляемая память.

Мои две текущие теории:

  • фрагментация LOH. Если я отвлекся от просматриваемого просмотра и нажму пару кнопок, половина из ~ 1,5 ГБ будет исправлена. Все еще слишком много, но тем не менее интересно.
  • Некоторая странная привязка WPF. Мы используем привязку данных для отображения этих изображений, и я не являюсь экспертом в отношении того, как эти элементы управления WPF работают.

Если у кого-то есть какие-либо теории или советы по профилированию, я был бы чрезвычайно благодарен, поскольку (конечно) мы находимся в сжатом сроке, и я немного борюсь за то, чтобы эта заключительная часть была выполнена и работала. Я думаю, что я был испорчен отслеживанием утечек памяти на С++... кто бы подумал?

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

4b9b3361

Ответ 1

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

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

BitmapImage сохраняет ссылку на исходный поток, поэтому он сохраняет объект MemoryStream живым. К сожалению, несмотря на то, что MemoryStream.Dispose вызывается, он не освобождает массив байтов, который обтекает поток памяти. Таким образом, в этом случае битмап ссылается на поток, который ссылается на буфер, который может занимать много места на большой куче объекта. Не существует истинной утечки памяти; когда больше нет ссылок на растровые изображения, все эти объекты будут (в конечном итоге) собираться мусором. Но так как битмап уже сделал свою собственную частную копию изображения (для рендеринга), кажется довольно расточительным иметь теперь ненужную оригинальную копию растрового изображения, все еще находящегося в памяти.

Кроме того, какую версию .NET вы используете? До .NET 3.5 SP1 была известная проблема, в которой BitmapImage может вызвать утечку памяти. Обходной путь состоял в том, чтобы вызвать Freeze в BitmapImage.

Ответ 2

Где вы закрываете и удаляете поток памяти? Может быть, GC должен работать намного сложнее, чтобы освободить ресурсы, переместив несколько поколений до выполнения деструкторов на объекте (которые обычно называют dispose, если вы забыли сделать это).

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