Почему происходит утечка памяти, когда я не делаю .Dispose () растровые изображения, которые я .Save () в MemoryStream? - PullRequest
3 голосов
/ 12 января 2012

Скажем, я создаю растровое изображение

Bitmap bitmap = new Bitmap(320, 200);

Когда я записываю его в какой-то поток (в моем случае это HttpResponseStream, как указано HttpListenerResponse), все в порядке:

bitmap.Save(stream, ImageFormat.Png);

Мне не нужно bitmap.Dispose (), ресурсы, используемые растровым изображением, будут очищены автоматически.Проблема с прямой записью Png в поток без возможности поиска состоит в том, что это может привести к Общая ошибка произошла в GDI + , которая произошла со мной, когда я попробовал мое приложение Asp в Azure.Вот как теперь выглядит мой код:

using (MemoryStream ms = new MemoryStream())
{
  bitmap.Save(ms, ImageFormat.Png);
  ms.WriteTo(stream);
}

Теперь, если я не буду bitmap.Dispose (), это приведет к утечке.

Перефразированный вопрос, чтобы получить более конкретные ответы: Почему утечка памяти Bitmap зависит от типа потока, в котором я ее сохраняю?

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

Ответы [ 3 ]

5 голосов
/ 12 января 2012

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

Пропустил последнюю часть вашего вопроса.Один из способов «установить и забыть» - создать класс-оболочку, который предоставляет экземпляр растрового изображения, но реализует деструктор, который удаляет экземпляр растрового изображения.Этот деструктор будет означать, что класс растровых данных неявно удаляется при сборке мусора.

В заключение: любой объект, который вы создаете, который реализует IDisposable, ДОЛЖЕН быть удален вашим кодом.Дипоза никогда не будет вызвана безоговорочно.Даже в вашем первом примере.То, что вы сохраняете данные в поток, не означает, что память была освобождена.Большую часть времени хорошей идеей является добавление объекта в тот же сегмент кода, который его создал.Это облегчает чтение кода за счет повышения прозрачности кода.

2 голосов
/ 12 января 2012

Я думаю, что проблема заключается в предположении, что GC волшебным образом очистит ваши объекты. Однако, это может никогда не произойти, и вот что, я думаю, может происходить:

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

Таким образом, вы оставляете свое растровое изображение без дела, чтобы сборщик мусора собирал его на досуге. Это хорошо работает для многих объектов, потому что вскоре возникает достаточное давление памяти, чтобы сборщик мусора собирал их для повторного использования памяти. Но GC смотрит на управляемую кучу и говорит: «Утилизируя неиспользуемые объекты, я могу восстановить только 64 байта памяти. Я не буду беспокоиться». Он не видит гигабайты неуправляемых ресурсов, только несколько байтов в своей куче.

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

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

... Бродить:

В старину было две проблемы с указателями.

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

Таким образом, в .net они переименовали «указатель» в «ссылку», добавили ГХ и сделали вид, что , что проблема больше не существует. За исключением того, что ссылки могут все еще быть нулевыми, и программистам все еще приходится отслеживать и управлять своими ресурсами, чтобы избежать утечек - только немного реже. Я думаю, что это плохо - это делает нас ленивыми и неэффективными, фактически не устраняя основную проблему, поэтому он возвращается и кусает нас, и мы заканчиваем тем, что пишем множество логики Dispose, где у нас было просто «удалить» в наших деструкторах.

0 голосов
/ 12 января 2012

Вы должны утилизировать растровое изображение, чтобы освободить ресурсы GDI +.Это так просто.Это один из немногих случаев, когда требуется вызов Dispose.Если вы кэшируете свое растровое изображение, чтобы уменьшить доступ к диску, клонируйте изображение и используйте клон для сохранения в поток.Я настоятельно рекомендую смывать, закрывать и утилизировать поток.Когда закончите, установите переменные clone и stream на null.

...