【问题标题】:Run out of memory内存不足
【发布时间】:2011-05-21 18:26:12
【问题描述】:

我这里有一些代码

   private void Run()
    {
        MyClass c = new MyClass();
        c.Load(somepath1);
        using (StreamReader sr = new StreamReader(filepath))
        {
            string line = string.Empty;
            while ((line = sr.ReadLine()) != null)
            {
                using (Bitmap B = new Bitmap(line))
                {
                    Point p = SomeMethod(ref c, new Point());
                    using (MemoryStream ms = new MemoryStream())
                    {
                        B.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
                        using (Bitmap T = new Bitmap(new Bitmap(Image.FromStream(ms))))
                        using (Graphics g = Graphics.FromImage(T))
                        {
                            g.DrawEllipse(new Pen(Brushes.Red, 4), p.X - 5, p.Y - 5, 10, 10);
                            FileInfo fi = new FileInfo(somepath2);
                            T.Save(Path.Combine(somepath3, fi.Name));
                        }
                    }
                }
            }
        }
    } 

函数 SomeMethod 是:

    Point SomeMethod(ref MyClass c, Point mid)
    {
        float[] Mat = new float[9];
        Point p;

        c.Method1(Mat);
        c.Method2(Mat, out p);

        return p;
    }

我的班级是:

public class MyClass
{
    public void Method1(float[] Mat, out Point point)
    {
        //calculation point from values in Mat
    }

    public void Method2(float[] Mat)
    {
        //Do some Operation in Mat
    }

    public void Load(string FileName)
    {
        //Do Some Data Loading From a small file about 400 byte
    }
}

StreamReader sr 在文件路径中打开一个包含大约 400 行图像位置的文件,我读取它们并根据我的计算在它们上绘制一些东西,我没有使用任何外部库或任何不安全的代码。 问题是为什么我的内存不足??

-------------编辑--------

当程序启动时它使用大约 20mb 的内存,在调用 Run 之后内存使用量开始增加,如果我运行它大约 200 张图像,内存大约 1.7Gb 并且 Run 函数完成工作并且内存使用量回到 20mb

------------编辑------------ 在 MemoryStream 中保存位图 B 是因为图形不能使用索引像素格式的图像。 主要问题是垃圾收集器在这里做什么? 我没有应该保留在内存中的对象。

---------编辑----

例外是:

System.OutOfMemoryException was unhandled
  Message=Out of memory.
  Source=System.Drawing
  StackTrace:
       at System.Drawing.Graphics.CheckErrorStatus(Int32 status)
       at System.Drawing.Graphics.DrawImage(Image image, Int32 x, Int32 y, Int32 width, Int32 height)
       at System.Drawing.Bitmap..ctor(Image original, Int32 width, Int32 height)
       at System.Drawing.Bitmap..ctor(Image original)
       at WindowsFormsApplication1.Form1.buttonrun1_Click(Object sender, EventArgs e) in C:\Users\hamidp\Desktop\WindowsFormsApplication1\WindowsFormsApplication1\Form1.cs:line 115
       at System.Windows.Forms.Control.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnClick(EventArgs e)
       at System.Windows.Forms.Button.OnMouseUp(MouseEventArgs mevent)
       at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
       at System.Windows.Forms.Control.WndProc(Message& m)
       at System.Windows.Forms.ButtonBase.WndProc(Message& m)
       at System.Windows.Forms.Button.WndProc(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
       at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
       at System.Windows.Forms.NativeWindow.DebuggableCallback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
       at System.Windows.Forms.UnsafeNativeMethods.DispatchMessageW(MSG& msg)
       at System.Windows.Forms.Application.ComponentManager.System.Windows.Forms.UnsafeNativeMethods.IMsoComponentManager.FPushMessageLoop(Int32 dwComponentID, Int32 reason, Int32 pvLoopData)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoopInner(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.ThreadContext.RunMessageLoop(Int32 reason, ApplicationContext context)
       at System.Windows.Forms.Application.Run(Form mainForm)
       at WindowsFormsApplication1.Program.Main() in C:\Users\hamidp\Desktop\WindowsFormsApplication1\WindowsFormsApplication1\Program.cs:line 17
       at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
       at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
       at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart()
  InnerException: 

以及在行抛出的异常:

using (Bitmap T = new Bitmap(new Bitmap(Image.FromStream(ms))))

-------------------编辑-----------

我还在

之后添加了GC.Collect();这一行

while ((line = sr.ReadLine()) != null)

同样的错误再次发生。

【问题讨论】:

  • 您的图片尺寸是多少?您使用的是什么操作系统(32/64 位)?你是为什么平台编译的?
  • Windows 7 Professional 64 位,图像约为 350KB jpeg,使用 Microsoft Visual Studio 2010,WinForms。
  • 尝试在较小的子集上运行它,同时观察内存消耗。它会以指数方式爆炸,还是有痛阈?
  • 它以某种方式线性增加,当它大约 2GB 时我的内存不足
  • JPEG 是一种压缩格式。当它被加载到内存中成为一个位图时,它将被解压缩到完整大小。 350K JPEG 可以在内存中扩展为 1MB 到 10MB 的位图,具体取决于图像的复杂性。图像的像素宽度和高度将告诉您每个图像在内存中需要多少内存(如果每像素 32 位,则 w * h * 4 字节)

标签: c# .net memory


【解决方案1】:

很可能是因为您分配了很多您不处置的对象。

在这一行:

using (Bitmap T = new Bitmap(new Bitmap(Image.FromStream(ms))))

FromStream 调用分配了一个您从不释放的位图。内部 Bitmap 构造函数创建另一个您永远不会处理的位图。只有外部的Bitmap 被处理。

请记住,using 块仅释放您在该语句中直接分配的对象,而不是您创建并用作创建该对象的参数的任何对象。

当您可以只使用已加载的位图时,我看不到从位图创建位图的逻辑:

using (Bitmap T = (Bitmap)Image.FromStream(ms))

在这一行:

g.DrawEllipse(new Pen(Brushes.Red, 4), p.X - 5, p.Y - 5, 10, 10);

您创建了一个永远不会被释放的Pen 对象。将其放在 using 块中:

Using (Pen red = new Pen(Brushes.Red, 4)) {
  g.DrawEllipse(red, p.X - 5, p.Y - 5, 10, 10);
}

【讨论】:

  • 不过,这不应该是相关的。对象很快就无法访问了,垃圾收集器应该能够拾取它们,终结器处理它们,然后在下一次垃圾收集器扫描时将它们扔掉(终结对象不能立即被扫描)。我不明白为什么这会留下 2GB 无法收集的资源。
  • 那么.net 垃圾收集器在做什么?
  • @Adam Norberg:垃圾收集器在终结器运行之前无法移除任何未处理的对象,并且只有一个后台线程一次运行所有终结器。如果它不能以与创建图像相同的速率执行终结器,则内存将被填满。
  • @HPT:垃圾收集器删除不再使用的托管对象和已处置的一次性对象。如果您错过处置一次性对象,垃圾收集器必须运行它的终结器作为清理资源的备用措施,这是清理对象的效率低得多的方法。
  • @HPT:当不再使用对象时可以收集它们,但这并不意味着 GC 会立即删除每个对象。对象在被收集之前一直保留在内存中,这通常是在下一次收集时。可处置对象包含非托管资源,因此必须先处置它们,然后才能收集它们。您必须在一次性对象上调用Dispose 方法(或使用using 块),以便垃圾收集器可以删除它们。如果不这样做,垃圾收集器会将它们交给 finalzier 线程,因此它们将一直使用,直到 finalizer 线程可以处理它们。
【解决方案2】:

听起来您正在将应用程序编译或运行为 32 位或使用 ANY CPU

编译成64位,不会遇到32位应用的2GB进程限制。

【讨论】:

  • @HPT - 你确定你运行的是 64 位吗? Visual Studio 是一个 32 位应用程序,将其用作运行程序(即在调试期间)会将您的应用程序作为 32 位运行。
  • 我知道,即使在调试时或者即使我运行发布版本也会出现同样的问题。
【解决方案3】:

由于所有图像,您很可能内存不足。

我有一个我一直在玩的小屏幕捕获应用程序,当只处理 100 张图像时,它需要 2GB 以上的内存(物理 + 页面文件)。

尝试缩小它,看看当你只做 10 张图片时会发生什么。

【讨论】:

  • 当程序启动时,它使用大约 20mb 的内存,在调用 Run 之后内存使用量开始增加,如果我运行它大约 200 张图像,内存大约为 1.7Gb,Run 函数完成工作和内存使用恢复到 20mb。
  • 这与我所看到的行为相同。
【解决方案4】:

您的代码同时在内存中制作并保留至少 4 个位图副本。位图 B,内存流,位图 T 可能有两个副本,图形 g 可能还有另一个副本。这些位图有多大?

在 B.Save 之后,您实际上并不需要 Bitmap B 在内存中,但它会一直保留在内存中,直到构造它的 using 子句结束。您应该考虑重新排序代码,以便在不再需要对象时立即释放它们。即,至少将 B.Save() 之后的内容移到 B using 子句之外。

将位图保存到内存流似乎也很可疑。您将位图从磁盘加载到位图 B,然后将 B 保存到内存流(对 SomeMethod 的调用不会修改位图),然后从内存流中加载位图。为什么?位图 T 应与位图 B 相同。

新位图(新位图(Image.FromStream(...)))有什么用?这似乎毫无意义地浪费内存。

尝试像这样重新排序您的代码(未经测试):

while ((line = sr.ReadLine()) != null) 
{ 
    Bitmap T = null;
    try
    {
        MemoryStream ms = new MemoryStream()) 
        try
        { 
            Bitmap B = new Bitmap(line);
            try
            { 
                Point p = SomeMethod(ref c, new Point()); 
                B.Save(ms, System.Drawing.Imaging.ImageFormat.Bmp);
            }
            finally 
            {
                B.Dispose();
            }

            T = new Bitmap(Image.FromStream(ms));
        }
        finally
        {
            m.Dispose();
        }

        using (Graphics g = Graphics.FromImage(T)) 
        { 
            g.DrawEllipse(new Pen(Brushes.Red, 4), p.X - 5, p.Y - 5, 10, 10); 
            FileInfo fi = new FileInfo(somepath2); 
            T.Save(Path.Combine(somepath3, fi.Name)); 
        }
    }
    finally
    {
        if (T != null)
        {
            T.Dispose();
        } 
    }
} 

这种安排可以让 Bitmap B 和 memorystream M 等对象尽快释放。您嵌套的 using 语句使它们的存活时间比需要的时间长得多,如果 GC 在所有这些过程中启动,这将阻止 GC 收集它们。如果 GC 确实在这一切的中间启动,那么任何由于仍被实时变量(或封闭的 using 子句)引用而无法收集的对象都将被推送到下一个较旧的堆生成组,这意味着它在他们被考虑再次收集之前将需要更长的时间。

请注意,位图还涉及非托管资源 - GDI 位图句柄,它消耗 .NET GC 系统之外的系统内存,并且在 Bitmap.Dispose 之前不会释放。因此,即使位图对象本身没有被 GC 收集,我们在执行流程中更早地调用 Bitmap.Dispose 这一事实应该有助于减少内存压力,因为这将释放 GDI 位图句柄。

我认为这不会完全解决您的记忆问题,但应该会有所帮助。在处理本质上涉及大量内存消耗的代码时,您需要在管理事物何时分配和处置方面发挥更积极的作用。使用子句很方便,但 try..finally 子句更明确、更精确,IMO。

【讨论】:

  • 您的编辑没有改变任何东西。在每次循环迭代期间,您的代码仍会强制 4+ 个图像副本保留在内存中,并且由于垃圾收集器通常在函数退出之前不会启动,因此您的循环中会累积大量内存.
【解决方案5】:

我只是用GC.WaitForPendingFinalizers()替换GC.Collect(),问题就解决了。

【讨论】:

  • 他不应该同时打电话给GC.Collect()GC.WaitForPendingFinalizers()吗?
  • -1 您应该正确处理对象,而不是等待终结者来处理。
【解决方案6】:

使用GC.Collect()GC.WaitForPendingFinalizers() 不是解决此问题的正确方法。您收到此错误,因为您仍然有 Bitmap 对象。所以你必须删除它。

try
{
    IntPtr intPtrHBitmap = IntPtr.Zero; 
    BitmapSource _Source = System.Windows.Interop.Imaging.CreateBitmapSourceFromHBitmap(intPtrHBitmap, IntPtr.Zero, System.Windows.Int32Rect.Empty, System.Windows.Media.Imaging.BitmapSizeOptions.FromEmptyOptions());
}
catch (Exception ex)
{
    Console.WriteLine(ex.Message);
}
finally
{
    DeleteObject(intPtrHBitmap);
    _Source = null;
}

将以下代码添加到类级别,不要失败。

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

【讨论】:

    猜你喜欢
    • 2014-10-15
    • 2021-12-20
    • 2021-10-29
    • 1970-01-01
    • 2020-03-12
    • 2015-08-11
    • 2019-06-07
    • 2019-04-19
    • 2021-05-14
    相关资源
    最近更新 更多