6

我有下一个非常奇怪的情况和问题:

  • 用于图表编辑 (WPF) 的 .NET 4.0 应用程序。

  • 在我的 PC 上运行正常:8GB RAM,3.0GHz,i7 四核。

  • 在创建对象(主要是图表节点和连接器,以及所有撤消/重做信息)时,TaskManager 显示,正如预期的那样,一些内存使用“跳跃”(向上和向下)。

  • 在用户交互结束后,这些内存使用“跳转”也会继续执行。也许这是 GC 清理/重组内存?

  • 为了查看发生了什么,我使用了 Ants 内存分析器,但它在某种程度上阻止了在用户交互之后发生这些“跳跃”。

问题:在我的 beta 测试人员的一些慢速/弱笔记本电脑/上网本(速度低于 2GHz 和 RAM 低于 2GB)中使用几秒钟或几分钟后,它会冻结/挂起。我在考虑内存泄漏,但是...

编辑1:此外,内存使用量会不断增长直到崩溃(仅在慢速机器中)。

  • 在仅分配了 512MB RAM 的 Windows XP Mode 机器(Win 7 中的 VM)中,它可以在用户交互后没有内存使用“跳跃”的情况下正常工作(没有 GC 清理?!)。

编辑 2:当系统运行其他一些繁重的程序(如 Visual Studio、Office 和网页打开)时,问题会更糟。甚至连图表的第一个符号都无法创建,因为内存使用量像火箭一样飞向太空(数百 MB 在几秒钟内消耗)。有类似经历的人吗?他们的策略是什么?

所以,我真的遇到了很大的麻烦,因为我无法重现错误,只能看到这些奇怪的行为(内存跳跃),而应该向我展示正在发生的事情的工具却隐藏了问题(如“观察者悖论”)。

关于正在发生的事情以及如何解决它的任何想法?

编辑 3:Ants 内存分析器的此屏幕截图显示,如果来自非托管资源,则会大量消耗 ram(渐强)。

在此处输入图像描述

但是,什么东西能消耗这么多内存,这么快????!!!

4

3 回答 3

6

您所描述的对于 .NET 程序来说都是完全正常的行为,没有迹象表明您的代码有任何问题。

到目前为止,最大的问题是 TaskMgr.exe 并不是一个很好的程序来告诉您进程中发生了什么。它显示进程的“工作集”,这个数字与进程使用的内存量关系不大。

工作集是您的进程使用的 RAM 量。每个进程都有 2 GB 的虚拟内存用于代码和数据。即使在只有 512 MB RAM 的虚拟 XP 机器上。然而,所有这些进程都只有一定数量的 RAM 可供使用。在一台只有千兆字节的低端机器上。

显然,运行多个进程,每个进程都有千兆字节的虚拟内存和只有千兆字节的真实内存,这需要一些魔力。这是由操作系统提供的,Windows 虚拟化了 RAM。换句话说,它为每个进程创造了一种错觉,即它自己在具有 2 GB RAM 的机器上运行。这是通过一种称为分页的功能来完成的,每当一个进程需要读取或写入内存时,操作系统都会抓取一块 RAM 来提供物理内存。

不可避免地,它必须从另一个进程中取出一些RAM,以便您可以使用它。之前在该 RAM 块中的任何内容都需要保留。这就是分页文件的作用,它存储被分页的 RAM 内容。

显然,这不是免费的,磁盘非常慢,分页是一项昂贵的操作。这就是为什么当你要求它们运行几个大型程序时,低级机器表现不佳的原因。TaskMgr.exe 中也可以看到对此的真正衡量标准,但您必须添加它。查看+选择列并勾选“页面错误增量”。在您的流程运行时观察此数字。当您看到它出现峰值时,您可以预期您的程序会大大减慢并且显示的内存使用情况会迅速变化。

解决您的意见:

创建对象...... TaskManager 显示,正如预期的那样,一些内存使用“跳跃”

是的,您正在使用 RAM,因此工作集会增加。

这些内存使用“跳跃”在用户交互结束后仍然执行

没有灌篮,但其他进程有更多的时间来执行,依次使用 RAM 并将你的一些进程淘汰。检查页面错误增量列。

我使用了 Ants 内存分析器,但在某种程度上它可以防止在用户交互之后发生这些“跳跃”。

是的,内存分析器专注于程序的实际内存使用情况,即虚拟内存类型。他们在很大程度上忽略了工作集,您对此无能为力,而且这个数字毫无意义,因为它实际上取决于正在运行的其他进程。

存在内存使用量不断增长直到崩溃的情况

这可能是垃圾收集器的副作用,但这并不典型。您可能只是看到 Windows 正在修剪您的工作集,丢弃页面,这样您就不会消耗太多。

在仅分配了 512MB RAM 的 Windows XP Mode 机器(Win 7 中的 VM)中,它工作正常

这可能是因为您没有在该 WM 上安装任何会争夺 RAM 的大型程序。XP 还被设计为在内存很少的机器上运行良好,在 256 MB 的机器上运行流畅。Vista/Win7 绝对不是这种情况,它们旨在利用现代机器硬件。像 Aero 这样的功能很吸引眼球,但非常昂贵。

当系统运行其他一些繁重的程序时,问题会更糟

是的,您正在与需要大量 RAM 的其他进程竞争。

当内存使用像火箭一样起飞时,甚至无法创建图表的第一个符号

是的,您看到页面被映射回 RAM,从分页文件和 ngen-ed .ni.dll 文件重新加载。再次快速增加您的工作集。您还将看到页面错误增量数达到峰值。

综上所述,您的 WPF 程序只会消耗大量内存,并且需要马力才能正常运行。这不容易解决,需要进行相当大的重新设计才能降低资源需求。所以只要把系统要求放在盒子上,这样做是完全正常的。

于 2012-04-16T18:02:27.293 回答
3

这表明您可能会创建很多“垃圾” - 基本上,创建并让许多对象快速超出范围,但这需要足够长的时间才能进入 Gen1 或 Gen2。这给 GC 带来了很大的负担,这反过来又会导致许多系统冻结和挂起。

为了查看发生了什么,我使用了 Ants 内存分析器,但它在某种程度上阻止了在用户交互之后发生这些“跳跃”。

这个分析器(ANTS)可以掩盖这种行为的原因是它每次拍摄内存快照时都会强制执行一次完整的 GC。这将使它看起来没有内存“泄漏”(因为没有),但不会显示系统上的总内存压力。

PerfView之类的工具可用于调查进程运行期间 GC 的行为。这将帮助您跟踪发生的 GC 数量,以及您在该时间点的应用程序状态。

于 2012-04-06T01:53:36.850 回答
2

如果没有看到您的代码,很难确切地知道发生了什么,但这里有一些建议:

首先,关于垃圾收集器的一些信息。最重要的是要知道 GC 是不确定的,你无法知道它什么时候会运行。即使调用 GC.Collect() 也只是一个建议,而不是命令。Gen0 堆用于本地范围的对象,并且经常被收集。如果一个对象在 Gen0 集合中幸存下来,它将被移动到 Gen1 堆。稍后,Gen1 堆将被收集,如果一个对象在收集中幸存下来,它将被移动到不太频繁收集的 Gen2 堆。出于这个原因,如果您分配了许多使其进入 Gen1 或 Gen2 堆的对象,则可能会在内存图中看到锯齿模式。

使用Process Explorer之类的工具检查托管堆(Gen0、Gen1、Gen2 和大对象堆)的大小并找出内存所在的位置。如果您有很多短期对象(Gen1 堆),请考虑一种重用内存而不是重新分配内存的方法 -对象池之类的东西非常适合。

此外,尝试将托管堆的总大小与应用程序的总私有字节数进行比较。私有字节包括应用程序分配的托管和非托管内存。如果托管堆的大小和私有字节之间存在很大差异,则很可能您的应用程序正在分配未正确处理的非托管对象(通过图形对象、流等)。查找实现 IDisposable 但没有调用 Dispose() 的对象。

另一个问题可能是堆碎片。如果您的应用程序正在分配无法放入当前堆的大型对象,它将向操作系统请求更多内存。避免这种情况的解决方案是分配较小的块或内存,或者以顺序块而不是随机分配它们(想想数组与链表)。像 ANTS 内存分析器这样的工具应该能够告诉您这种情况正在发生。

@ReedCopsey 推荐 PerfView(或它的前身 CLR Profiler)是一个很好的建议,它可以让您更好地了解内存是如何分配的。

于 2012-04-14T20:39:01.337 回答