System.Drawing.Bitmap中的C#OutOfMemoryException
有没有什么办法可以在运行时获得更多关于OutOfMemoryException的细节?或者,这个异常能不能被封装的try/catch抓住,而是在调用堆栈上方更高的try/catch?我无法使用WinDBG重现,因此它必须是我可以从应用程序登录的东西。System.Drawing.Bitmap中的C#OutOfMemoryException
我为长时间的解释表示歉意,但有很多可能的原因要消除,我解释一下。
我已经阅读了OutofMemoryException的所有可能性,并基本消除了所有这些异常。通常情况下,应用程序运行良好,但偶尔只在某些计算机上运行,我得到一个OutOfMemoryException。由于这些报告在当地是不可复制的,我只能看到日志。但我有相当多的细节。
有什么奇怪:
- 任何可能逻辑在附近被分配内存是在一个try/catch,但例外被视为未处理(追了更高的调用堆栈)
- 没有使用StringBuffers
- 即使重新启动并重新启动应用程序后,也会发生异常。
- 仅仅几分钟后就会发生异常,只有大约30MiB的内存分配,大小不超过1.5MiB。
- 经验证的应用程序(为“任何”处理器构建)以64位运行。
- 不缺磁盘空间(270Gb空闲)并启用页面文件。
- 似乎不是一个可能的LoH碎片问题。
这在最近的应用程序的不同部分发生了几次。第一次,我总结说有一个损坏的.NET程序集,因为当第一次加载System.Web.Serialization程序集时,异常发生了。我可以确定它是在第一次使用该组件的方法调用期间发生的。重新映像计算机(与原始设置相同)并更新窗口可解决此问题。
但是,对我来说,第二种情况,不同的客户,在几天内发生的情况似乎不太可能是腐败。这一个发生在一个没有装配组件的位置。我现在正在重新思考第一个错误。我所知道的:
- 它发生在一个线程池线程(System.Timers.Timer,[Statthread])
- 少量活跃线程(< 5)的
- 它发生周围的时间1MiB文件已下载。这被读入一个MemoryStream,所以它可以像2MiB一样大。然后将其提供给System.Drawing.Bitmap构造函数,从而生成一个大约8MiB的位图。但是,这都是在捕获System.Exception的try/catch中。唯一不在try/catch中的是返回byte []引用,它应该只是一个引用副本,而不是任何内存分配。
- 在此之前没有其他重要的内存分配已完成。在本地版本中查看应该以相同方式运行的堆,只显示应用图标和位于小对象堆上的几十个对象。
- 它可以在具体的输入的特定系统上重复使用。但是,这些系统是相互克隆的。只有明显的变化才是Windows更新的顺序。
- 我正在运行的程序集已签名。没有确保它没有损坏的校验和吗?所有的系统组件都一样?我看不出这个实例是如何被损坏的dll或数据解释的。
- 在异常时查看调用堆栈意外无益。我在下面的代码中指出引发异常的地方。
-
有一些使用COM对象。然而,在正常情况下,我们运行了几个星期的应用程序没有记忆的问题,当我们得到这些例外,他们几乎是即时的,只用大约20相对轻量级的COM对象(IUPnPDevice)
// Stack Trace indicates this method is throwing the OutOfMemoryException // It isn't CAUGHT here, though, so not in the try/catch. // internal void Render(int w, int h) { if (bitmap != null) { bitmap.Dispose(); bitmap = null; } if (!String.IsNullOrEmpty(url)) { // this information is printed successfully to log, with correct url // exception occurs sometime AFTER this somehow. Logger.Default.LogInfo("Loading {0}", url); // when file contents changed (to go from 1MiB to 500MiB, the error went away) byte[] data = DownloadBinaryFile(url); if (data != null) { try { Bitmap bmp; using (var ms = new MemoryStream(data)) { bmp = new Bitmap(ms); } bitmap = bmp; } catch (Exception) { // We do not catch anything here. Logger.Default.LogWarning("WARNING: Exception loading image {0}", url); } } // // if we had any errors, just skip this slide // if (bitmap == null) { return; } // QUESTION EDIT: // in the problematic version, there was actually an unnecessary // call here that turns out to be where the exception was raised: using(Graphics g = Graphics.FromImage(bitmap)) { } } } // calling this would trigger loading of the System.Web assembly, except // similar method has been called earlier that read everything. Only // class being used first time is the BinaryReader, which is in System.IO // and already loaded. internal static byte[] DownloadBinaryFile(String strURL, int timeout = 30000) { try { HttpWebRequest myWebRequest = HttpWebRequest.Create(strURL) as HttpWebRequest; myWebRequest.KeepAlive = true; myWebRequest.Timeout = timeout; myWebRequest.ReadWriteTimeout = timeout; myWebRequest.UserAgent = "Mozilla/4.0 (compatible; MSIE 5.01; Windows NT 5.0)"; Encoding encode = System.Text.Encoding.GetEncoding("utf-8"); using (HttpWebResponse myWebResponse = myWebRequest.GetResponse() as HttpWebResponse) { if (myWebResponse.StatusCode != HttpStatusCode.OK) { Logger.Default.LogWarning("WARNING: Response {0} loading {1}", myWebResponse.StatusCode, strURL); return null; } using (Stream receiveStream = myWebResponse.GetResponseStream()) { using (BinaryReader readStream = new BinaryReader(receiveStream)) { // this extension method uses MemoryStream, but seems irrelevant since we don't catch anything here. return readStream.ReadAllBytes(); } } } } catch (Exception e) { // we do not catch anything here. Logger.Default.LogError("ERROR: Exception {0} loading {1}", e.Message, strURL); } return null; }
所以后毕竟,我回到我的开头提问。我可以检查OutOfMemoryException对象的任何已知属性,还是在抛出异常后调用我可以调用的这些属性来缩小这个范围?
And ..有没有任何理由OutOfMemoryException不会被第一个try/catch捕获,但会被捕获进一步调用堆栈?
谢谢大家。答案有些奇怪,我有一些细节错误,很难弄清楚。该错误是在这里:
Bitmap bmp;
using (var ms = new MemoryStream(data))
{
bmp = new Bitmap(ms);
}
bitmap = bmp;
在对位图文件的构造函数的言论,我发现这一点:
你必须保持开放流的位图的寿命。
显然,在构造后立即关闭MemoryStream违反了这个规定。之间和我实际使用位图之间的垃圾收集显然是创建错误。 (编辑:实际上,似乎边界存在于1MiB左右,其中FromStream函数最初只能解压缩JPEG文件的大部分内容,对于JPEG < 1MiB,整个图像将被解压缩,并且在初始化后它不会真正使用流。对于较大的JPEG,它不会超出第一个1MiB,直到需要这些像素为止)
我很难想象为什么微软会这样做。我不希望保持原有的流开,要么(这是一个HTTP连接),所以只有我看到的解决方案是克隆位:
// Create a Bitmap object from a file.
using (var ms = new MemoryStream(data))
{
bmp = new Bitmap(ms);
Rectangle cloneRect = new Rectangle(0, 0, bmp.Width, bmp.Height);
System.Drawing.Imaging.PixelFormat format = bmp.PixelFormat;
this.bitmap = bmp.Clone(cloneRect, bmp.PixelFormat);
}
是什么导致了我的长令人沮丧的搜索和的排除关键的一点是,在客户端机器上执行的代码只是稍微老一点的版本,只有微妙的变化。 Graphics.FromImage()在前一版本的位图上被调用,但已被删除。尽管如此,该版本在绝大多数时间都运行良好。
由于字节数组(数据),您可以期望mem碎片。让DownloadBinaryFile返回一个流。您可以选择根据数据大小返回MemoryStream或FileStream。 –
你能告诉我们堆栈跟踪吗?这可能是由碎片引起的,请参阅[https://stackoverflow.com/a/8564247/4684493](https://stackoverflow.com/a/8564247/4684493)以了解可能的解决方法。你也可以使用[MemoryFailPoint](https://msdn.microsoft.com/en-us/library/system.runtime.memoryfailpoint.aspx)来帮助你调试 – Hintham
,最终可能会出现碎片。但是,这发生在第一次或第二次大堆分配上。 –