Bitmap.Save, огромная утечка памяти

У меня есть приложение, в котором я беру растровое изображение, сжимаю его с помощью GZipStream и отправляю через сокет, все в памяти. Я отследил утечку памяти грязного отморозка до следующей строки:

frame.Save(inStream, jpegCodec, parameters);

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

  1. Что вызывает это
  2. Как я могу это исправить

Вот мой полный метод Write() в моем классе FrameStream, где находится утечка.

/// <summary>
    /// Writes a frame to the stream
    /// </summary>
    /// <param name="frame">The frame to write</param>
    public void Write(Bitmap frame) {
        using (EncoderParameter qualityParameter = new EncoderParameter(Encoder.Quality, 50L)) {
            using (EncoderParameters parameters = new EncoderParameters(1)) {
                parameters.Param[0] = qualityParameter;

                ImageCodecInfo[] codecs = ImageCodecInfo.GetImageEncoders();
                ImageCodecInfo jpegCodec = null;

                foreach (ImageCodecInfo codec in codecs) {
                    if (codec.MimeType == "image/jpeg") {
                        jpegCodec = codec;
                        break;
                    }
                }

                using (MemoryStream inStream = new MemoryStream()) {
                    frame.Save(inStream, jpegCodec, parameters); // HUUUGE Memory Leak
                    Byte[] buffer = new Byte[inStream.Length];
                    inStream.Read(buffer, 0, buffer.Length);

                    using (MemoryStream outStream = new MemoryStream()) {
                        using (GZipStream gzipStream = new GZipStream(outStream, CompressionMode.Compress)) {
                            gzipStream.Write(buffer, 0, buffer.Length);
                        }

                        Byte[] frameData = outStream.ToArray();
                        Byte[] packet = new Byte[15 + frameData.Length];
                        Byte[] frameLength = BitConverter.GetBytes(frameData.Length);

                        Array.Copy(frameLength, 0, packet, 0, frameLength.Length);
                        Array.Copy(frameData, 0, packet, 15, frameData.Length);

                        m_Socket.Send(packet);
                    }
                }
            }
        }
    }

person David Anderson    schedule 16.05.2009    source источник
comment
Вы сами смотрели на источник с помощью Reflector?   -  person matt b    schedule 16.05.2009


Ответы (5)


Я предлагаю запустить ваш код под CLR Profiler, чтобы найти источник утечки. Если это управляемый объект любого типа (даже неуправляемый ресурс), при условии, что ошибка не связана с утечкой неуправляемого дескриптора управляемого типа, вы сможете увидеть, где находится утечка. Если это в коде фреймворка, вы, вероятно, можете обойти это, используя отражение и P/Invoke.

Например, тип Icon в некоторых случаях приводит к утечке Win32 HICON. Обходной путь для этого — вручную удалить HICON, вызвав функцию DeleteObject, используя дескриптор, предоставленный Icon.

person Katelyn Gadd    schedule 16.05.2009

Хорошо, после того, как я испробовал всевозможные идеи и мысли, а также множество других методов. Я, наконец, попробовал простое:

using (frame) {
    frame.Save(outStream, jpegCodec, parameters);
}

И что ж, это сработало, и утечка памяти устранена. Я попытался принудительно вызвать сборщик мусора, удалить растровое изображение вручную, используя P/Invoke DeleteObject, ничего не сработало, но сработало использование оператора using. Так что это заставляет меня задаться вопросом, что происходит под капотом с оператором использования, который я упускаю....

person David Anderson    schedule 16.05.2009
comment
Он просто вызывает Dispose. Вы действительно не показали достаточно кода, чтобы сообщить нам, как вы вызывали метод Write выше - я подозреваю, что вы на самом деле не избавлялись от растрового изображения. - person Jon Skeet; 16.05.2009
comment
В коде, который я разместил выше, я пропустил строку frame.Dispose() после m_Socket.Send(...), но вызов dispose для фрейма не имел значения в моем тестировании, он по-прежнему выделял много памяти - person David Anderson; 16.05.2009
comment
После дальнейшего изучения выясняется, что это накопление памяти растровых изображений в очереди, поскольку сокет в приложении не может отправлять пакеты достаточно быстро, чтобы не отставать от того, сколько растровых изображений поставлено в очередь. - person David Anderson; 19.05.2009

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

Кроме того, вы можете вызвать сборщик мусора после удаления растрового изображения (даже если это дорогостоящая операция): GC.Collect();

Bitmap содержит неуправляемые ресурсы - GC не всегда «в курсе» с ними. Вот интересная ссылка на класс Bitmap (с точки зрения компактной среды): http://blog.opennetcf.com/ctacke/PermaLink,guid,987041fc-2e13-4bab-930a-f79021225b74.aspx

person Demi    schedule 16.05.2009
comment
как побочная мысль, возможно, можно было бы подкрасться и использовать поток для сохранения. Когда поток завершается, особенно вместе с удалением объекта, удерживающего ресурсы, вы должны иметь возможность восстановить утечку памяти. Я могу ошибаться, но, возможно, стоит попробовать, если ничего не помогает. - person Demi; 16.05.2009

Я не очень разбираюсь в сокетах. Однако я знаю один из способов остановить утечку памяти с помощью класса Image — заморозить растровое изображение. Надеюсь, этот пост может предоставить вам дополнительную информацию. Может ли MemoryStream.Dispose дать сбой? Тем самым создавая утечку памяти.

person kevindaub    schedule 16.05.2009

Вы вызываете метод .Dispose() вашего объекта Graphics? Это вызовет утечку памяти. РЕДАКТИРОВАТЬ: После того, как вы записали byte[], теперь вы можете использовать .Dispose() объекта Bitmap.

person Wayne Hartman    schedule 16.05.2009
comment
Если вы закомментируете frame.Save(..), утечки памяти не будет, и все графические объекты будут удалены и учтены. Из моих поисков утечки памяти bitmap.save были отмечены как ошибка в фреймворке my msft. не знаю, есть ли исправление, это то, что я ищу - person David Anderson; 16.05.2009
comment
@David: Возможно, вы захотите добавить эту информацию в вопрос, чтобы избежать путаницы. - person Henk Holterman; 16.05.2009