System.Drawing.Bitmap中的C#OutOfMemoryException

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捕获,但会被捕获进一步调用堆栈?

+3

由于字节数组(数据),您可以期望mem碎片。让DownloadBinaryFile返回一个流。您可以选择根据数据大小返回MemoryStream或FileStream。 –

+0

你能告诉我们堆栈跟踪吗?这可能是由碎片引起的,请参阅[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

+0

,最终可能会出现碎片。但是,这发生在第一次或第二次大堆分配上。 –

谢谢大家。答案有些奇怪,我有一些细节错误,很难弄清楚。该错误是在这里:

  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()在前一版本的位图上被调用,但已被删除。尽管如此,该版本在绝大多数时间都运行良好。