【问题标题】:Memory leak, but where?内存泄漏,但在哪里?
【发布时间】:2014-10-25 18:35:33
【问题描述】:

我不明白这里泄漏了什么

using GDI = System.Drawing;

public partial class MainWindow : Window
{
    [DllImport("gdi32.dll")]
    private static extern bool DeleteObject(IntPtr obj);

    public MainWindow()
    {
        InitializeComponent();

        var timer = new DispatcherTimer() { Interval = TimeSpan.FromMilliseconds(50) };
        timer.Tick += (s, a) =>
        {
            using (var bitmap = new GDI.Bitmap(1000, 1000))
            {
                var hbitmap = bitmap.GetHbitmap();
                var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
                image.Freeze();
                DeleteObject(hbitmap);
            }
        };
    timer.Start();
}

bitmap?弃置。 hbitmap?已删除。 image?冻结,它不是IDisposable

事实上,这个应用程序会崩溃(在我的电脑上运行大约 20 秒后)

System.Drawing.dll 中出现“System.OutOfMemoryException”类型的未处理异常

附加信息:内存不足。

有什么想法吗?

【问题讨论】:

  • 这是一个非常快的循环,您是否在尝试删除之前没有得到hbitmap
  • @Sayse,你有什么建议?要检查null 还是什么?
  • 使用内存分析器?
  • /\ Profiler 听起来不错,为了测试(仅!)可能值得添加Thread.Sleep
  • @SriramSakthivel,我认为给出的例子足以让更有经验的人告诉我哪里出了问题。在这种情况下,我对内存分析器可以告诉我的内容有特快版和非常有限的知识......

标签: c# wpf memory-leaks


【解决方案1】:

据我所知,没有泄漏。问题是您分配大型 C# 对象的速度很快,而垃圾收集器启动得太晚了?

以下是一些相关主题:

Avoiding OutOfMemoryException during large, fast and frequent memory allocations in C#

这是一个有用的线程:

Garbage Collection not happening even when needed

如果你踢 GC.Collect(with generation 0..3),你的内存消耗将是固定的:

    while (true)
    {
        Thread.Sleep(5);

        using (var bitmap = new GDI.Bitmap(1000, 1000))
        {
            var hbitmap = bitmap.GetHbitmap();
            var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty,
                BitmapSizeOptions.FromEmptyOptions());

            image.Freeze();


          DeleteObject(hbitmap);
        }

        Console.WriteLine("Current memory consumption" + GC.GetTotalMemory(false));
        GC.Collect(3);
    }

和输出:

Current memory consumption156572
Current memory consumption156572
Current memory consumption156572
Current memory consumption156572

真正的问题是 GC 不知道您的非托管分配,即使您释放它们也是如此。您需要添加内存压力并让 GC 知道:

 var width = 1000;
                var height = 1000;

                using (var bitmap = new GDI.Bitmap(width, height))
                {
                    var hbitmap = bitmap.GetHbitmap();
                    var allocatedSize = width*height*4; // each pixel takes ~4 bytes?!
                    GC.AddMemoryPressure(allocatedSize);

                    var image = Imaging.CreateBitmapSourceFromHBitmap(hbitmap, IntPtr.Zero, Int32Rect.Empty,
                        BitmapSizeOptions.FromEmptyOptions());

                    image.Freeze();


                    DeleteObject(hbitmap);
                    GC.RemoveMemoryPressure(allocatedSize);
                }

让 GC 了解底层非托管内存有助于确保 GC 在正确的位置启动。

【讨论】:

  • 那个压力对我来说是新事物,谢谢。我真的很喜欢你的第二个解决方案(没有直接GC 电话),工作顺利!
  • 注意到第二个解决方案的一个 事情:内存分配可能会增长(有时甚至导致OutOfMemoryException)并停在那里,保持分配的大量内存。计时器不会发生这种情况,仅当用户操作发生更新时(如果用户移动鼠标并且在每个MouseMove 事件上重绘某些内容,则可能会发生上述问题)。也许与优先级有关。无论如何,这个问题的解决方案是使用你的第一个解决方案(直接触发GC.Collect(3))在定时/计数的基础上(例如,每 10 次重绘 - 收集)。
  • Erti +Erti @Erti - 谢谢你教我极限垃圾收集。这太棒了。完美解决困扰我的问题。一千加给你。
【解决方案2】:

垃圾收集器可能没有足够快地释放已处理的位图。您每 20 秒创建 400 个 1000*1000 位图,这可能会消耗高达 1.6GB 的内存。也许尝试添加第二个计时器,它每 1000 毫秒运行一次,调用 GC.Collect()。

【讨论】:

  • 第二个定时器可能运行得不够足够快,但感谢您的想法(实际上是正确的,代码中没有内存泄漏,简单的太多分配)。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-04-09
  • 1970-01-01
  • 2012-05-22
相关资源
最近更新 更多