在这篇文章中,我将讨论一个更棘手的异常:System.OutOfMemoryException。顾名思义,当.NET应用程序内存不足时抛出异常。有在MSDN文章中,OutOfMemoryException有两种不同的原因:
- 试图将StringBuilder对象扩展到其StringBuilder.MaxCapacity属性定义的长度之外。此类错误通常会附加以下消息:“内存不足,无法继续执行程序。”
- 公共语言运行库(CLR)无法分配足够的连续内存。
在我过去的.NET开发生涯中,我没有遇到过第一个问题,为什么我不会花太多时间在上面。简而言之,这样做将导致System.OutOfMemoryException:
StringBuilder sb = new StringBuilder(1, 1); sb.Insert(0, "x", 2);
为什么?好吧,我们定义了一个新的StringBuilder,它的最大容量是一个字符,然后尝试插入两个字符。这样一来,让我们来谈谈为什么您可能会遇到异常:因为CLR无法分配您的程序正在请求的内存。要将此转换为您妈妈会理解的内容,您的应用程序使用的资源比可用的资源还要多。
.NET程序经常使用大量内存。NET中的内存管理基于垃圾收集,这意味着您不需要告诉框架何时清理。当.NET检测到不再需要某个对象时,会将其标记为删除,并在下次运行垃圾收集器时将其删除。这也意味着OutOfMemoryException并不总是等同于一个问题。32位进程有2 GB的可用虚拟内存,64位进程最多有8 TB。如果在64位操作系统上运行,请始终确保将应用程序编译为64位(它可能已经这样做了)。“内存不足”并不是指物理内存。区分大量内存使用和内存泄漏是很重要的。第一种情况可以接受,而第二种情况总是需要调试。
要开始调试OutOfMemoryException,我建议您通过任务管理器或使用perfmon.msc查看应用程序。这两个工具都可以跟踪当前的内存消耗,但是为了获得一个更好的概览,perfmon是最好的。启动时,右键单击图表区域,然后单击添加计数器。。。展开.NET CLR内存节点,然后单击“提交的字节总数”。最后,在“选定对象的实例”列表中选择要监视的进程,然后单击“确定”按钮。
在本文的其余部分中,我将使用并修改一个示例程序,向列表中添加字符串:
class Program { static void Main(string[] args) { try { var list = new List<string>(); int counter = 0; while (true) { list.Add(Guid.NewGuid().ToString()); counter++; if (counter%10000000 == 0) { list.Clear(); } } } catch (OutOfMemoryException e) { Environment.FailFast(String.Format($"Out of Memory: {e.Message}")); } } }
在当前状态下,程序不断向列表中添加字符串,并且每10000000次清除列表。在Perfmon中查看当前内存使用情况时,您将看到当前图片:
最好的垃圾收集。这里,我删除了对list.Clear()的调用:
class Program { static void Main(string[] args) { try { var list = new List<string>(); while (true) { list.Add(Guid.NewGuid().ToString()); } } catch (OutOfMemoryException e) { Environment.FailFast(String.Format($"Out of Memory: {e.Message}")); } } }
我们现在得到一张完全不同的照片:
程序继续分配内存,直到抛出System.OutOfMemoryException。该示例演示如何利用Perfmon监视应用程序的状态。就像电视上的厨师一样,我作弊,并为这篇文章做了一个例子。在你的例子中,你可能不知道是什么导致了内存的广泛使用。该内存分析器来救援了!
与任务管理器和Perfmon不同,内存分析器是帮助您找到内存问题或内存泄漏根本原因的工具。
.NET内存探查器很好地集成到Visual Studio中,单击“新建探查器>启动内存探查器”菜单项可以分析应用程序的原因。从前面的示例中,我们可以看到与Perfmon类似的图片:
这幅画看起来和以前很像。进程分配越来越多的内存(橙色和红色的线),进程抛出一个异常。在底部,显示从分析会话分配的所有对象,并对分配进行排序。查看顶行可以很好地指示是什么导致了泄漏。
在这个简单的例子中,很明显,如果出现问题,字符串会添加到列表中。但是,大多数程序都比向列表中添加随机字符串复杂得多。这就是.NET内存探查器(以及其他工具)中可用的快照功能显示其优点的地方。快照就像Windows中的还原点,是当前内存使用情况的完整图片。通过在进程运行时单击“收集快照”按钮,可以获得差异:
查看Live Instances>New列,很明显有人正在创建大量字符串。
我不希望这是一个.NET内存分析器的广告,所以请查看他们的文档,了解如何在.NET程序中分析内存的完整图片。另外,一定要检查上述替代产品。他们都有免费的试用版,所以试试看,挑选你最喜欢的。
我希望这篇文章为您提供了“一套非常特殊的技能”(抱歉)来帮助您调试内存问题。不幸的是,查找内存泄漏可能非常困难,需要一些培训和经验。