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)


arrow_upward
4
arrow_downward

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

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

person Katelyn Gadd    schedule 16.05.2009

arrow_upward
3
arrow_downward

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

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

arrow_upward
2
arrow_downward

Вы должны установить для растрового изображения значение 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

arrow_upward
0
arrow_downward

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

person kevindaub    schedule 16.05.2009

arrow_upward
0
arrow_downward

Вы вызываете метод .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