【问题标题】:Debugging OoM Exception调试 OoM 异常
【发布时间】:2012-03-20 13:56:10
【问题描述】:

编辑:添加代码(第 095 行的异常,第 5 次命中。)

    public DataTable ParseBarcodes(String[] files, BarcodeZoneScan[] scanParameters)
    {
        message = null;
        //gmseBitmap img = null;
        gmseBitmap rotImg = null;
        gmseBitmap parseImage = null;
        gmseBitmap tempImage = null;

        DataTable codes = new DataTable();
        codes.Columns.Add("PageNumber");
        codes.Columns.Add("Text");
        codes.Columns.Add("Type");
        codes.Columns.Add("RegionName");

        try
        {
            gmseBarcodeInfoCollection bcc;
            gmseBarcodeReaderParameter param = new gmseBarcodeReaderParameter();
            gmseLicense.License = "plaintext license key ommited";

            String dvImageName;

            int searchCount = 0;

            for (int dvCount = 0; dvCount < files.Length; dvCount++)
            {
                if (cancelled) //If cancelled, end the loops
                {
                    dvCount = files.Length;
                    break;
                }

                dvImageName = files[dvCount].ToString();
                using (gmseBitmap img = new gmseBitmap(dvImageName))
                {

                int framecount = img.GetFrameCount();

                for (int e = 0; e < framecount; e++)
                {
                    for (int j = 0; j < scanParameters.Length; j++)
                    {
                        if (scanParameters[j].Range == PageRange.All ||//All 
                            (scanParameters[j].Range == PageRange.Even && (searchCount == 0 || searchCount % 2 == 0)) || //even
                            (scanParameters[j].Range == PageRange.Odd && (searchCount != 0 && searchCount % 2 != 0)) ||
                            (scanParameters[j].Range == PageRange.First && searchCount == 0))
                        {
                            //Setup what barcodes are going to be search for

                            param.BarcodeType = 0;
                            if (scanParameters[j].BarcodeTypes == BarcodeType.All) //All
                            {
                                param.BarcodeType = (int)gmseBarcodeType.All;
                            }
                            else
                            {
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Code39) != 0) //Code 39
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.Code39;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Code11) != 0) //Code 11
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.Code11;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Code93) != 0) //Code 93
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.Code93;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Code128) != 0) //Code 128
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.Code128;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Ean8) != 0) //EAN 8
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.EAN8;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.Ean13) != 0) // EAN 13
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.EAN13;
                                if ((scanParameters[j].BarcodeTypes & BarcodeType.I2of5) != 0) //I2of5
                                    param.BarcodeType = param.BarcodeType | (int)gmseBarcodeType.i2of5;
                            }

                            param.IgnoreCheckSum = 1;
                            param.ReadMode = gmseBarcodeReadMode.WholeBitmap;

                            using (rotImg = new gmseBitmap(img.ExtractFrame(e)))
                            {

                                // do some basic image enhancement for better results
                                rotImg.ChangePixelFormat(System.Drawing.Imaging.PixelFormat.Format32bppArgb);

                                rotImg.SelectActiveFrame(e);

                                if (scanParameters[j].WholePage)
                                {
                                    parseImage = rotImg.ExtractFrame(e);
                                }
                                else
                                {
                                    using (tempImage = rotImg.ExtractFrame(e))
                                    {
                                        Rectangle convertedRect = returnConvertedRectangle(tempImage, scanParameters[j].Dimensions);
                                        if (convertedRect.IntersectsWith(new Rectangle(0, 0, tempImage.Width, tempImage.Height)))
                                        {
                                            //GC.Collect(); //Test so I can see what objects are still alive in dump
                                            parseImage = tempImage.CopyRectangle(convertedRect); //Exception here
                                        }

                                    }
                                }
                            }
                            //rotImg.Dispose();
                            //rotImg = null;

                            if (parseImage != null)
                            {
                                //Now we will apply the image enhancements:
                                if (scanParameters[j].Enhancements != ImageEnhancement.None)
                                {
                                    rotImg = EnhanceImage(parseImage, scanParameters[j].Enhancements);
                                    parseImage.Dispose();
                                    parseImage = null;
                                }

                                if ((scanParameters[j].BarcodeScanDirection & ScanDirection.LeftToRight) != 0 && !cancelled)
                                {
                                    if (parseImage == null)
                                    {
                                        tempImage = new gmseBitmap(rotImg.Image, 1);
                                    }
                                    else
                                    {
                                        tempImage = new gmseBitmap(parseImage.Image, 1);
                                    }

                                    bcc = tempImage.ReadBarcodes(param);
                                    foreach (gmseBarcodeInfo bc in bcc)
                                    {
                                        addBarcode(codes, new object[] { searchCount, bc.Text, gmseBarcodeTypeConvert(bc.BarcodeType), scanParameters[j].ZoneName });
                                    }

                                    tempImage.Dispose();
                                    tempImage = null;
                                }
                                if ((scanParameters[j].BarcodeScanDirection & ScanDirection.RightToLeft) != 0 && !cancelled)
                                {
                                    if (parseImage == null)
                                    {
                                        tempImage = new gmseBitmap(rotImg.Image, 1);
                                    }
                                    else
                                    {
                                        tempImage = new gmseBitmap(parseImage.Image, 1);
                                    }

                                    tempImage.RotateFlip(RotateFlipType.Rotate180FlipNone);

                                    bcc = tempImage.ReadBarcodes(param);
                                    foreach (gmseBarcodeInfo bc in bcc)
                                    {
                                        addBarcode(codes, new object[] { searchCount, bc.Text, gmseBarcodeTypeConvert(bc.BarcodeType), scanParameters[j].ZoneName });
                                    }

                                    tempImage.Dispose();
                                    tempImage = null;
                                }
                                if ((scanParameters[j].BarcodeScanDirection & ScanDirection.TopToBottom) != 0 && !cancelled)
                                {
                                    if (parseImage == null)
                                    {
                                        tempImage = new gmseBitmap(rotImg.Image, 1);
                                    }
                                    else
                                    {
                                        tempImage = new gmseBitmap(parseImage.Image, 1);
                                    }

                                    tempImage.RotateFlip(RotateFlipType.Rotate90FlipNone);

                                    bcc = tempImage.ReadBarcodes(param);
                                    foreach (gmseBarcodeInfo bc in bcc)
                                    {
                                        addBarcode(codes, new object[] { searchCount, bc.Text, gmseBarcodeTypeConvert(bc.BarcodeType), scanParameters[j].ZoneName });
                                    }

                                    tempImage.Dispose();
                                    tempImage = null;
                                }
                                if ((scanParameters[j].BarcodeScanDirection & ScanDirection.BottomToTop) != 0 && !cancelled)
                                {
                                    if (parseImage == null)
                                    {
                                        tempImage = new gmseBitmap(rotImg.Image, 1);
                                    }
                                    else
                                    {
                                        tempImage = new gmseBitmap(parseImage.Image, 1);
                                    }

                                    tempImage.RotateFlip(RotateFlipType.Rotate270FlipNone);

                                    bcc = tempImage.ReadBarcodes(param);
                                    foreach (gmseBarcodeInfo bc in bcc)
                                    {
                                        addBarcode(codes, new object[] { searchCount, bc.Text, gmseBarcodeTypeConvert(bc.BarcodeType), scanParameters[j].ZoneName });
                                    }

                                    tempImage.Dispose();
                                    tempImage = null;
                                }

                                if (parseImage != null)
                                {
                                    parseImage.Dispose();
                                    parseImage = null;
                                }
                                if (rotImg != null)
                                {
                                    rotImg.Dispose();
                                    rotImg = null;
                                }
                            }
                        }
                    }

                    searchCount++;

                    if (cancelled) //If cancelled, end the loops
                    {
                        e = framecount;
                        dvCount = files.Length;
                    }
                }
                } //end using img
                //img.Dispose();
                //img = null;
            }

        }
        catch (Exception ex)
        {
            message = ex.Message;
        }
        finally
        {
            if (img != null)
            {
                img.Dispose();
                img = null;
            }
            if (rotImg != null)
            {
                rotImg.Dispose();
                rotImg = null;
            }
            if (tempImage != null)
            {
                tempImage.Dispose();
                tempImage = null;
            }
            if (parseImage != null)
            {
                parseImage.Dispose();
                parseImage = null;
            }
        }

        if (!String.IsNullOrEmpty(message))
            throw new Exception(message);

        return codes;
    }

我们使用这个 GMSE Imaging 插件来协助 OCR 从扫描中读取条形码,它通过将图像旋转 10 度直到被读取来处理歪斜。发现了一个错误,扫描不同尺寸的纸张会引发错误。

我将它从我们的主程序跟踪到我们的一个 DLL,在那里我发现它正在捕获 OutOfMemoryException。

原始 TIF 为 300kb,但为了旋转图像,需要进行大量复制。 (4个位图之间) 但是,我已经通过程序跟踪并监视了本地人,并且似乎在故障循环的方法之前,每个位图都被正确处理并分配了 null。

我也尝试在循环末尾添加GC.Collect()

我在一台 32 位 W7 机器上,我读过它每个对象有 2GB 的限制,有大量的 RAM,所以没有什么缺乏这方面的。 一直在任务管理器上看,我的内存使用量只有 1.72GB 到 1.78GB。

这是一个很难研究的问题,因为 OoM 似乎是一个不寻常的错误。 我想知道是否有人对处理这种异常有任何建议?我不是 Visual Studio 大师,有没有一种简单的方法可以监控资源/内存使用情况?

或者知道我可以使用哪些实用程序来提供帮助?

在这里转储错误信息,不确定代码 sn-ps 在这种情况下会有多大用处...

System.OutOfMemoryException was caught
Message=Out of memory.
Source=System.Drawing
 StackTrace:
   at System.Drawing.Bitmap.Clone(Rectangle rect, PixelFormat format)
   at gmse.Imaging.gmseBitmap.CopyRectangle(Rectangle r)
   at ImagingInterface.ImagingFunctions.ParseBarcodes(String[] files, BarcodeZoneScan[] scanParameters) in C:\Working\Scan.backup\Global Dlls\v2.6.0.02\ScanGlobalDlls\ImagingInterface\ImagingFunctions.cs:line 632

内部异常:

(目前正在阅读更多关于 GC/内存管理http://msdn.microsoft.com/en-us/library/ee851764.aspx

处理step of this guide,在“即时”窗口中使用 SOS 调试器,目的是查明异常是由托管代码还是非托管代码生成的。


上面的步骤表明这是托管代码的问题,因为显示了来自 SOS 的异常类型。

Exception object: 39594518
Exception type: System.OutOfMemoryException
Message: <none>
InnerException: <none>
StackTrace (generated):

The Heapdump I took 似乎不像我预期的那样是成千上万的位图。不是 100% 确定如何解释转储,所以看看我能找到什么。

现在不知道从这里搬到哪里! (搜索..)


编辑:

我一直在尝试将this blog 中的课程应用于我的问题。

从 PerfMon 开始

此图显示了我的程序从执行到捕获异常的过程。

前两个尖峰出现在触发解析扫描图像后,最后一个下降出现在捕获异常时。

问:比较所有堆中的 Virtual Bytes、Private Bytes 和 #Bytes 的曲线,它们是相互跟随还是发散? #Bytes 在所有不同的堆中的意义是什么? (就像我的公寓一样)

使用 !address -summary 检查内存 MEM_IMAGE 对应的 PrivateBytes(113MB) 非常准确。

问:大部分内存都去了哪里(哪个 RegionType)? RegionUsageFree 87.15% RegionUsageIsVAF 5.64% (Busy 43.89%) [通过 VirtualAlloc 分配的内存] RegionUsageImage 5.54% (Busy 43.13%) [映射到作为可执行映像一部分的文件的内存。]

在加载了 SOS 的 WinDbg 中,我做了一个 !DumpHeap

//...
7063424c     1201        28824 System.Collections.ArrayList
706228d4      903        28896 System.EventHandler
7062f640     1253        30072 System.RuntimeType
6ec2be78      833        31216 System.Windows.Forms.PropertyStore+IntegerEntry[]
6ec2b0a4      654        34008 System.Windows.Forms.CreateParams
7063547c      318        35472 System.Collections.Hashtable+bucket[]
6ec2aa5c      664        37184 System.Windows.Forms.Control+ControlNativeWindow
70632938      716        40400 System.Int32[]
6c546700       48        49728 System.Data.RBTree`1+Node[[System.Data.DataRow, System.Data]][]
70634944       85        69600 System.Byte[]
6ec2b020      931        85972 System.Windows.Forms.PropertyStore+ObjectEntry[]
6c547758      156       161616 System.Data.RBTree`1+Node[[System.Int32, mscorlib]][]
705e6c28     2107       238912 System.Object[]
00305ce8       18       293480      Free
7062f9ac     5842       301620 System.String
Total 35669 objects

这里是最占用内存的对象。 我希望有些东西会像拇指疼痛一样突出,比如大量的位图或其他东西。有什么在这里尖叫“我表现异常!”对任何人? (我正在尝试单独检查最上面的那些可疑的东西,但最好能进一步缩小可能的罪魁祸首)

This page (Address summary explained) 帮了大忙。 但是 C# 是我的第一语言,所以我之前没有调试内存问题的经验。想知道我是否走在正确的轨道上(GC 是个问题吗?),因为我还没有发现任何明确的迹象。


答案:问题是在 3rd 方库中引起的。我无能为力。 经过深思熟虑和一些测试,其中包含仅涉及产生错误的方法的精简代码。

奖励给我认为我从中学到最多的东西。

【问题讨论】:

  • 这是由 GDI+ 错误代码产生的异常,它与垃圾收集器无关,与 RAM 无关。当 GDI+ 在 unmanaged 虚拟内存中找不到足够大的孔以容纳位图时,它往往会崩溃。到目前为止,不处理位图是最典型的原因,而且位图太大了。您的问题中没有关于真正原因的提示。 64 位操作系统是百元的解决方案。
  • 好的,我明白你在说什么,汉斯。我以前没有遇到过这种特殊情况。感谢您指出。
  • @HansPassant 感谢您的回复,我的印象是,如果我可以在即时窗口中!printexception,那么必须从托管代码生成异常。所以我可能会浪费更多时间寻找错误的地方。
  • 这一行 using (rotImg = new gmseBitmap(img.ExtractFrame(e))) 缺少一个 using,因为 ExtractFrame 正在分配一个新的位图,之前没有任何机会释放它下一次 GC 收集。另外,我会避免在 using 语句中使用全局变量,因为它不便于阅读(并且不能很好地处理异常)。尝试在 using 语句中声明在 using 语句中使用的变量,而不是在之前。
  • 我的意思是两件事:1)一般来说是的,我建议你在 using 语句范围内声明变量,这将有助于有一个更清晰的代码来确保所有 Dispose (无需实际编写任何显式的 Dispose 调用),并在异常中幸存,以及 2)对于此特定行,您应该有两个 using 语句,而不是只有一个,因为 ExtractFrame 分配了一个位图。

标签: c# debugging out-of-memory sos


【解决方案1】:

好的,添加的信息会有所帮助。问题不在于您的程序使用了太多内存,它使用了太少。垃圾收集堆中的数据非常少。这对于操作位图的程序来说并不少见。 Bitmap 类是 GDI+ 函数的一个非常小的包装器,它仅使用 GC 堆中的少量字节。因此,您可以在填满 gen #0 堆并触发垃圾回收之前创建大量位图。这也可以从 Perfmon 中看到,您想查看 .NET CLR Memory, Gen 0 Collections 计数器。一个健康的程序在工作时每秒会触发大约 10 次收集。

没有收藏集是好的,但是当没有收藏集时,还有一些其他的东西不起作用。终结器线程永远不会运行。终结器对于释放内存以外的非托管资源很重要。就像操作系统句柄和托管对象持有的任何非托管内存指针一样。位图有那些。

首先要做的是运行Taskmgr.exe,进程选项卡。单击查看 + 选择列并勾选句柄、用户对象和 GDI 对象。在程序运行时观察这些计数器。如果您看到一个无限制地向上攀爬,那么您遇到的问题可能会导致 GDI+ 生成 OOM 异常。 GDI 对象是常见原因。

仔细检查您的代码并检查您是否对不再使用的任何图像或位图调用 Dispose()。当心一些微妙的,比如分配 PictureBox 的 Image 属性。如果它不为空,您必须处理旧的。这当然很痛苦,而且很容易错过。所以使用一个简单的策略,计算你创建的位图的数量,比如说,在第 100 次调用 GC.Collect + GC.WaitForPendingFinalizers() 来触发收集和终结器扫描。

【讨论】:

  • 嗯,没有一个计数器持续上升。我做了几次运行以获得基于从程序开始到达到峰值的上升的平均值。 GDI,提高 20。用户对象提高 70,句柄提高 5 到 6。
【解决方案2】:

过去我一直使用ANTS Memory Profiler 来解决这类问题。它不是免费的,但它对于托管代码中的内存/引用泄漏非常有效。您只需在应用程序应该处于稳定状态时拍摄几张快照并查看更改。

【讨论】:

  • 谢谢你的推荐,我会玩的:)
【解决方案3】:

您可以安全地在img 变量周围添加一个using 块,并通过一些重构,您可以对您声明的其他图像变量执行相同的操作。

这至少应该使代码更具可读性,并减少忘记在 finally 块中添加 1 的机会;我什至可以为解决问题做出贡献。不过,您似乎是在手动处理每个创建的图像对象。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2020-04-24
    • 1970-01-01
    • 2017-11-19
    • 1970-01-01
    • 1970-01-01
    • 2018-06-13
    • 2012-12-31
    • 2011-09-11
    相关资源
    最近更新 更多