使用 C# 的图像加载内存泄漏

Posted

技术标签:

【中文标题】使用 C# 的图像加载内存泄漏【英文标题】:Image loading memory leak with C# 【发布时间】:2010-12-15 10:48:17 【问题描述】:

我的应用程序中存在内存泄漏问题,该问题会加载大量图像。我对 C# 相当陌生,并认为我的内存泄漏问题已经过去。我无法找出问题所在 - 也许我正在使用一些我没有正确处理的非托管模块?

为了说明我的问题,我简化了导致问题的核心并将其移至一个干净的项目中。请注意,这都是愚蠢的代码,并没有反映它来自的原始应用程序。在测试应用程序中,我有 2 个按钮,触发了两个事件。

按钮 1 - 创建:将对象设置为数据上下文。这将加载图像并通过将对象设置为 DataContext 来使它们保持活动状态:

var imgPath = @"C:\some_fixed_path\img.jpg";
DataContext = new SillyImageLoader(imgPath);

按钮 2 - 清理:我的理解是,如果我放开持有 SillyImageLoader 的引用,该引用再次持有图像,那么它将被删除。我还显式地触发垃圾收集,只是为了在删除引用后立即查看内存量。

DataContext = null; 
System.GC.Collect();

在测试时,我正在加载一个 974KB 的 jpeg 图像。保存 30 个位图表示可以将我的应用程序的内存使用量从 ~18MB 提高到 ~562MB。好的。但是当我点击清理时,内存只下降到~292MB。如果我重复 Create+CleanUp,我将剩下大约 250MB 的内存。所以很明显,某些东西仍然被某人持有。

这里是 SillyImageLoader 代码:

namespace MemoryLeakTest

    using System;
    using System.Drawing;
    using System.Windows;
    using System.Windows.Interop;
    using System.Windows.Media.Imaging;

    public class SillyImageLoader
    
        private BitmapSource[] _images; 

        public SillyImageLoader(string path)
        
            DummyLoad(path);
        

        private void DummyLoad(string path)
        
            const int numberOfCopies = 30;
            _images = new BitmapSource[numberOfCopies];

            for (int i = 0; i < numberOfCopies; i++)
            
                _images[i] = LoadImage(path);
            
        

        private static BitmapSource LoadImage(string path)
        
            using (var bmp = new Bitmap(path))
            
                return Imaging.CreateBitmapSourceFromHBitmap(
                    bmp.GetHbitmap(),
                    IntPtr.Zero,
                    Int32Rect.Empty,
                    BitmapSizeOptions.FromEmptyOptions());
                        
        
    

有什么想法吗?问题似乎出在 BitmapSource 上。仅保留位图没有内存泄漏。我使用 BitmapSource 能够将其设置为图像的 Source 属性。我应该这样做吗?如果是这样 - 我仍然想知道内存泄漏的答案。

谢谢。

【问题讨论】:

谁在从 LoadImage 返回的 BitmapSource 上调用 Dispose? 我想这个,确实基于它发布了一个答案,但我在 BitmapSource 上看不到处置(我已经删除了答案) 您如何监控应用程序的内存使用情况?任务管理器? 任务管理器可能会产生误导。当您分配一个对象时,.net 将分配一些内存 - 这最终将涉及要求窗口为进程分配一些内存。此内存成为您的应用程序“工作集”的一部分 - 分配给进程的总内存。当您释放对该对象的最后一个引用并且 GC 释放它时,clr 堆上的内存将被标记为空闲,但它不会返回供 Windows 使用 - 它仍然是工作集的一部分。由于任务管理器会显示您的工作集的大小,因此您可能会对内存使用情况产生误导 在工具方面,我可以推荐 YourKit Profiler (yourkit.com/.net/profiler/index.jsp) 个人许可证非常便宜,它确实有助于追踪未释放的资源以及哪些对象保留了对它们的引用。 【参考方案1】:

当你打电话时

bmp.GetHbitmap()

创建位图的副本。您需要保留对指向该对象的指针的引用并调用

DeleteObject(...)

就可以了。

来自here:

备注

您有责任致电 GDI DeleteObject 方法释放 GDI 位图对象使用的内存。


您可以通过使用BitmapImage 而不是 BitmapSource 来避免复制位图的麻烦(和开销)。这使您可以一步加载和创建。

【讨论】:

这是绝对正确的,让内存问题消失了!谢谢!并感谢其他有类似答案的人!你说得对,我可以在这个特定的例子中使用 BitmapImage。但是,在我的实际应用程序中,我没有图像文件的路径,而是从 3rd 方库的另一个对象中获取位图。我对此无能为力。所以我不能使用 Uri - 我需要使用位图..【参考方案2】:

您需要在 GetHBitmap() 返回的 IntPtr 指针上调用 GDI DeleteObject 方法。从该方法返回的IntPtr 是一个指向内存中对象副本的指针。这必须使用以下代码手动释放:

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private static BitmapSource LoadImage(string path)


    BitmapSource source;
    using (var bmp = new Bitmap(path))
    

        IntPtr hbmp = bmp.GetHbitmap();
        source = Imaging.CreateBitmapSourceFromHBitmap(
            hbmp,
            IntPtr.Zero,
            Int32Rect.Empty,
            BitmapSizeOptions.FromEmptyOptions());

        DeleteObject(hbmp);

    

    return source;

【讨论】:

【参考方案3】:

似乎当你调用 GetHBitmap() 时你负责释放对象

[System.Runtime.InteropServices.DllImport("gdi32.dll")]
public static extern bool DeleteObject(IntPtr hObject);

private void DoGetHbitmap() 

    Bitmap bm = new Bitmap("Image.jpg");
    IntPtr hBitmap = bm.GetHbitmap();

    DeleteObject(hBitmap);

我猜 BitmapSource 不负责释放这个对象。

【讨论】:

以上是关于使用 C# 的图像加载内存泄漏的主要内容,如果未能解决你的问题,请参考以下文章

使用图像时的内存泄漏

使用 NSURLSession.downloadTaskWithURL 时的内存泄漏

大量内存泄漏 - CocoaLibSpotify

带有 .NET 的 C++ dll:内存泄漏是确定的吗?

Unity - Android, C# - C++ 内存泄漏

iPhone 应用程序内存泄漏与 NSMutableArray 中的 UIImages