我有下一个非常奇怪的情况和问题:
用于图表编辑的.NET 4.0应用程序(WPF)。
在我的电脑上运行正常:8GB内存,3.0GHz,i7四核。
在创建对象(主要是图表节点和连接器,加上所有撤消/重做信息)时,TaskManager会按预期显示一些内存使用情况“跳转”(向上和向下)。
这些mem-usage“jumps”在用户交互结束后也会继续执行。也许这是GC清理/重新组织内存?
为了了解发生了什么,我使用了Ants mem profiler,但在某种程度上它阻止了用户交互后发生的“跳跃”。
问题:在我的beta测试者的一些慢/弱laptos /上网本中使用几秒或几分钟后它会冻结/挂起(速度低于2GHz且RAM低于2GB)。我在考虑内存泄漏,但是......
编辑1:此外,存在内存使用量增长和增长直到崩溃的情况(仅在慢速机器中)。
编辑2:当系统运行其他一些繁重程序时(例如Visual Studio,Office和Web页面打开),问题更严重。甚至没有图表的第一个符号可以创建,而内存使用起飞像火箭到太空(几秒钟消耗数百MB)。有类似经历的人吗?他们的策略是什么?
所以,我真的遇到了很大麻烦,因为我无法重现错误,只能看到这些奇怪的行为(内存跳转),并且该工具应该告诉我发生了什么是隐藏问题(比如“观察者的悖论”)
有关正在发生的事情以及如何解决问题的任何想法?
编辑3:蚂蚁内存分析器的这个屏幕截图显示,如果来自非托管资源,ram(在渐强中)的巨大消耗。
但是,什么可以消耗如此多的内存,如此之快?!!!
答案 0 :(得分:5)
您所描述的是.NET程序的完全正常行为,没有迹象表明您的代码有任何问题。
到目前为止,最大的问题是TaskMgr.exe不是一个非常好的程序,可以告诉你在你的过程中发生了什么。它显示进程的“工作集”,这个数字与进程使用的内存量几乎没有关系。
工作集是您的进程使用的RAM量。每个进程都有2千兆字节的虚拟内存用于代码和数据。即使在只有512 MB RAM的虚拟XP盒子上也是如此。但是,所有这些进程只有一定数量的RAM可供使用。在一台低至一千万亿的机器上。
显然有多个进程在运行,每个进程都有几千兆字节的虚拟内存和只有一千兆字节的真实的内存需要一些魔力。这是由操作系统提供的,Windows虚拟化RAM。换句话说,它会在具有2 GB RAM的计算机上为它自己运行的每个进程创建幻觉。这是通过一个名为 paging 的功能完成的,只要进程需要读取或写入内存,操作系统就会抓取一大块RAM来提供物理内存。
不可避免地,它必须从另一个进程中取出远离一些RAM,以便它可以为您提供。无论以前在那块RAM中需要保留什么。这就是分页文件所做的,它存储了被分页的RAM内容。
显然,这不是免费的,磁盘非常慢,分页是一项昂贵的操作。这就是为什么当你要求他们运行几个大型程序时,低级机器表现不佳的原因。在TaskMgr.exe中也可以看到真正的测量方法,但您必须添加它。查看+选择列并勾选“页面错误增量”。在您的流程运行时观察此数字。当你看到它飙升时,你可以期待你的程序减速很快,显示的内存使用量会迅速改变。
解决你的意见:
创建对象......正如预期的那样,TaskManager显示一些内存使用“跳转”
是的,你正在使用RAM,因此工作集会上升。
这些mem-usage“jumps”在用户交互结束后仍然执行
没有扣篮,但其他进程有更多时间执行,依次使用RAM并将你的一些人撞出来。检查Page fault delta列。
我使用过Ants mem profiler,但在某种程度上它会阻止用户交互后发生“跳跃”。
是的,内存分析器专注于程序的实际内存使用情况,即虚拟内存类型。他们在很大程度上忽略了工作集,没有任何你能做的事情,而且数字毫无意义,因为它实际上取决于其他进程正在运行的内容。
存在内存使用量增长并逐渐增长直至崩溃的情况
这可能是垃圾收集器的副作用,但这不是典型的。您可能只是看到Windows修剪您的工作集,丢弃页面,因此您不会消耗太多。
在Windows XP模式机器(Win 7中的VM)中,只分配了512MB的RAM,它可以正常工作
这可能是因为你没有在WM上安装任何可以竞争RAM的大型程序。 XP也被设计为在内存非常少的机器上运行良好,在256 MB的机器上运行平稳。这绝对不是Vista / Win7的情况,它们的设计充分利用了现代机器硬件。像Aero这样的功能很好看,但非常昂贵。
当系统运行其他重程序时,问题更严重
是的,您正在与需要大量RAM的其他进程竞争。
当内存使用像火箭一样起飞时,甚至不能创建图表的第一个符号
是的,您看到页面被映射回RAM,从页面文件和ngen-ed .ni.dll文件重新加载。再次快速增加工作量。您还会看到Page fault delta number峰值。
总而言之,你的WPF程序只消耗大量内存,需要马力才能运行良好。这不容易修复,需要进行大幅度的重新设计以降低资源需求。因此,只需将系统要求放在盒子上,这样做是完全正常的。
答案 1 :(得分:3)
这表明你可能会创造很多“垃圾” - 基本上,创建和让许多对象快速超出范围,但这需要足够长的时间才能进入Gen1或Gen2。这给GC带来了很大的负担,这反过来会导致冻结并在许多系统上挂起。
为了看看发生了什么,我使用了Ants mem profiler,但有些它可以防止用户交互后发生“跳跃”。
此探查器(ANTS)特别可能掩盖此行为的原因是每次执行内存快照时它都会强制执行完整的GC。这会使它看起来没有内存“泄漏”(因为没有),但没有显示系统上的总内存压力。
可以使用像PerfView这样的工具来调查GC在进程运行时的行为。这有助于您跟踪发生的GC数量以及该时间点的应用程序状态。
答案 2 :(得分:2)
如果没有看到您的代码,很难确切知道发生了什么,但这里有一些建议:
首先,有关垃圾收集器的一些信息。最重要的是要知道GC是非确定性的,你不知道它何时会运行。甚至调用GC.Collect()只是一个建议,而不是一个命令。 Gen0堆用于本地范围的对象,并且经常收集。如果一个对象在Gen0集合中存活,它将被移动到Gen1堆。稍后将收集Gen1堆,如果一个对象存活,它将被移动到收集频率较低的Gen2堆。因此,如果要分配大量进入Gen1或Gen2堆的对象,可以在内存图中看到锯齿图案。
使用像Process Explorer这样的工具检查托管堆的大小(Gen0,Gen1,Gen2和大对象堆)并找出内存的保存位置。如果你有很多短期对象(Gen1堆)想到一种重用内存而不是重新分配的方法 - 像object pool之类的东西可以很好地适用于它。
另外,尝试将托管堆的总大小与应用程序的总私有字节进行比较。专用字节包括应用程序分配的托管和非托管内存。如果托管堆的大小与私有字节之间存在很大差异,则可能是您的应用程序正在分配未正确处理的非托管对象(通过图形对象,流等)。查找实现IDisposable的对象,但不要调用Dispose()。
另一个问题可能是堆碎片。如果您的应用程序正在分配无法容纳到当前堆中的大对象,那么它将从操作系统请求更多内存。避免这种情况的解决方案是分配较小的块或内存,或者在顺序块中而不是随机分配它们(想想数组与链表)。像ANTS Memory Profiler这样的工具应该可以告诉你这是在发生。
@ReedCopsey对PerfView(或它的前身CLR Profiler)的推荐是一个很好的建议,它会让你更好地了解你的内存是如何分配的。