Утечка памяти при повороте изображения

#c# #.net #winforms #memory-leaks

#c# #.net #winforms #утечки памяти

Вопрос:

Я создал короткую тестовую программу для непрерывного поворота изображения .jpg в форме, начиная с этого примера:

 private void DrawImagePointF(PaintEventArgs e)
{
             
    // Create image.
    Image newImage = Image.FromFile("SampImag.jpg");
             
    // Create point for upper-left corner of image.
    PointF ulCorner = new PointF(100.0F, 100.0F);
             
    // Draw image to screen.
    e.Graphics.DrawImage(newImage, ulCorner);
}
 

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

Это код, вызывающий утечку памяти:

 using System;
using System.Drawing;
using System.Windows.Forms;

namespace Picture_rotation {
    public partial class Form1 : Form {
        Image jpg;
        float angle = 0f;
        PointF ulCorner = new PointF(50f, 50f);
        public Form1() {
            InitializeComponent();
        }

        private void Form1_Load(object sender, EventArgs e) {
            jpg = Image.FromFile("picture.jpg");
        }

        private void DrawImagePointF(PaintEventArgs e) {

            ((Bitmap)jpg).SetResolution(e.Graphics.DpiX, e.Graphics.DpiY);

            e.Graphics.DrawImage(Rotate(jpg, angle), ulCorner);

        }

        // interval = 100
        private void timer1_Tick(object sender, EventArgs e) {

            angle =   angle % 360;
            Invalidate();       // to invoke Form1_Paint()

        }

        private Bitmap Rotate(Image original, float angle) {

            Bitmap bmp = new Bitmap(original.Width, original.Height);
            Graphics g = Graphics.FromImage(bmp);
            g.TranslateTransform((float)bmp.Width / 2, (float)bmp.Height / 2);
            g.RotateTransform(angle);
            g.TranslateTransform(-(float)bmp.Width / 2, -(float)bmp.Height / 2);
            g.InterpolationMode = System.Drawing.Drawing2D.InterpolationMode.HighQualityBicubic;
            g.DrawImage(original, new Point(0, 0));
            g.Dispose();
            return bmp;     // memory leak?
        }

        private void Form1_Paint(object sender, PaintEventArgs e) {
            DrawImagePointF(e);
        }
    }
}
 

Комментарии:

1. Вы удаляете растровое изображение в какой-то момент? или просто создавать кучу и не беспокоиться об этом?

2. Проблема с материалом, имеющим неуправляемую память, заключается в том, что, хотя он использует память, GC ее не видит. Для них эти экземпляры (изображение, растровое изображение) КРОШЕЧНЫЕ — очень маленькие. В основном указатель на неуправляемую память. Следовательно, IDisposable — в КАКОЙ-ТО МОМЕНТ сработает GC. Это может произойти, когда управляемая память достигает 1 ГБ, что может произойти, когда общая память программы достигает десятков ГБ.

3. Утилизируйте экземпляр BitMap, возвращенный Rotate после его использования

4. using var bmp = Rotate(jpg, angle); всегда используйте оператор using, когда это возможно. Это создает pesudo, наконец, в IL, и такое будет удаляться, даже если есть исключение. тогда меньше строк кода, попробуйте наконец, легко поддерживать и легче читать намерение

5. Вы должны утилизировать все, что имеет dispose. Обычно это указывает на то, что он обрабатывает и обрабатывает неуправляемые ресурсы, дополнительно позволяет вам неявно контролировать его жизненный цикл, и вам не нужно полагаться на неопределенный характер сборщика мусора, который, в свою очередь, будет запускаться, когда ему захочется, так, как ему хочется.

Ответ №1:

Вы уверены, что есть утечка памяти?

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

Объем памяти вашего приложения увеличится до 2? ГБ, а затем сжимается до 200? МБ после каждого GC.

Комментарии:

1. Если вы создаете много растровых изображений и не удаляете их, вам понравится ошибка GDI out of memory достаточно скоро

2. Да, вы были правы. GC действительно очищает память примерно через 5 минут, когда использование ОЗУ составляет около 2,5 ГБ, а затем оно возвращается к 20-30 МБ, и все начинается сначала. Я не пробовал, освобождал ли GC оперативную память раньше при запуске, например, 10 экземпляров программы с 16 ГБ оперативной памяти. Однако, удаляя bmp, программа использует только 17 МБ, что означает, что сотни экземпляров могут выполняться одновременно.