.NET 可用内存使用情况(如何防止过度分配/释放内存给操作系统)

Posted

技术标签:

【中文标题】.NET 可用内存使用情况(如何防止过度分配/释放内存给操作系统)【英文标题】:.NET Free memory usage (how to prevent overallocation / release memory to the OS) 【发布时间】:2012-04-05 23:56:44 【问题描述】:

我目前正在开发一个网站,该网站大量使用缓存数据来避免往返。 在启动时,我们得到一个“大”图(数百个不同种类的对象)。 这些对象通过 WCF 检索并反序列化(我们使用协议缓冲区进行序列化) 我正在使用 redgate 的内存分析器来调试内存问题(内存似乎不适合我们“在”完成初始化并最终得到此报告后需要多少内存

现在我们可以从这份报告中收集到的是:

1) .NET 分配的大部分内存都是免费的(它可能在反序列化过程中被正确分配,但现在它是免费的,我希望它返回到操作系统)

2) 内存是碎片化的(这很糟糕,因为每次我刷新现金时,我都需要重做需要大量内存的反序列化过程,这反过来又会创建可能由于碎片化而引发 OutOfMemoryException 的大对象)

3)我不知道为什么空间是碎片的,因为当我查看大对象堆时,只有 30 个实例,15 个 object[] 直接附加到 GC 并且与我完全无关,1 是char 数组也直接附加到 GC 堆,其余 15 个是我的,但不是造成这种情况的原因,因为如果我在代码中将它们注释掉,我会得到相同的报告。

所以我的问题是,我该怎么做才能更进一步?我不太确定在调试/工具中要寻找什么,因为我的内存似乎是碎片化的,但不是我自己,而且 .net 分配了大量可用空间,我无法释放。

另外,请确保您在回答之前充分理解问题,我不是在寻找一种在 .net (GC.Collect) 中释放内存的方法,而是将 .net 中已经可用的内存释放到系统以及对所述内存进行碎片整理。

请注意,缓慢的解决方案很好,如果可以手动对大堆进行碎片整理,我会全力以赴,因为我可以在 RefreshCache 结束时调用它,如果运行需要 1 或 2 秒也没关系。

感谢您的帮助!

我忘记了一些注意事项: 1) 该项目是一个 .net 2.0 网站,我在 .net 4 池中运行它得到相同的结果,如果我在 .net 4 池中运行它并将其转换为 .net 4 并重新编译,则同上。

2) 这些是发布版本的结果,因此调试版本不是问题。

3)这可能非常重要,我在 webdev 服务器中根本没有遇到这些问题,只有在 IIS 中,在 webdev 中我得到的内存消耗与我的实际消耗相当接近(更多,但不是 5- 10 倍以上!)

【问题讨论】:

您服务器上的应用程序池和您的开发服务器之间的处理器架构有什么不同吗? 很抱歉我的句子在我读回来时不是很清楚,我并不是说开发服务器作为单独的服务器,而是在同一台服务器上但在 iis 之外运行(在 asp.net 开发服务器中,集成在 Visual Studio 2010 中)。 为了完整性(在这两种情况下,因为它是同一台机器),这是在 Windows 2008 R2 X64 上,网站是 AnyCPU 同样的主题,在 IIS Express 下呢?我希望与 IIS 具有相同的行为。也不知道有没有用。 我不知道,但不会有太大变化,无论如何我都需要它在真正的 IIS 下运行。我只是希望声明 asp.net 开发服务器不显示这可能有助于调试这种情况。 (注意,如果有帮助,我很乐意在 iis express 下进行测试,但我不确定我是否应该将它安装在已经有 iis 的服务器上) 【参考方案1】:

我知道这不是您想听到的答案,但您不能强行将内存释放回操作系统。但是,您为什么要这样做?一旦物理内存不足,.NET 会将其堆释放回操作系统。但是,如果有足够的可用物理内存,.NET 将保留其堆,以便将来更快地分配对象。如果您真的想强制 .NET 将其堆释放回操作系统,我想您可以编写一个 C 程序,它只是 malloc 直到它耗尽内存。这应该会导致操作系统向 .NET 发出信号以释放其未使用的堆部分。

最好为 .NET 保留未使用的内存,以便您的应用程序具有更好的分配性能(因为运行时知道哪些内存是空闲的,哪些不是,分配可以只使用空闲内存而无需系统调用操作系统以获得更多内存)。

垃圾收集器负责对堆进行碎片整理。每隔一段时间(通常在收集运行期间),如果确定需要这样做,它将在堆周围移动对象。 (这就是为什么 C++/CLI 有 pin_ptr 构造用于“固定”对象)。

虽然内存碎片化通常不是什么大问题,因为它提供了快速的随机访问。

至于您的 OutOfMemoryException,我没有很好的答案。通常我会怀疑你的旧对象图没有被收集(某处的某个对象持有对它的引用,“内存泄漏”)。但既然你使用的是分析器,那我就不知道了。

【讨论】:

您的代码都是托管的 C# 代码,对吧?您没有将指针固定到非托管代码中吗?否则,运行时应该为您整理内存碎片。 不,所有托管代码,我的应用程序也不需要更好的分配性能,它需要大约每天分配一次,这是一个非阻塞操作,这里的问题是它过度分配了多少( 5X 所需的)和内存碎片,afaik 不,GC 不会对导致 OutOfMemoryExceptions 的大对象堆进行碎片整理,我已经看到帖子抱怨无法触发大型对象堆碎片整理。问题是我每天刷新一次所有内容,并且由于碎片而每隔几天就会出现 OutOfMemoryException,这是一个很大的禁忌。 您可以尝试使用 malloc() 执行循环的简单 C 程序,一旦 malloc() 失败就会中断(返回值为 NULL)。然后退出程序。这应该迫使 .NET 释放其所有内存(但它也会使所有进程都执行与操作系统上运行的相同的操作)。 但这并不能解决碎片问题,并且 afaik GC 不会释放碎片位,只会释放完全释放的段,因此虽然它可能会释放一些过度分配的内存,但我仍然会被空闲的碎片内存卡住,可能会增加 OutOfMemoryExceptions,我真的希望有一个 GC.CompactAllGen() 如果它们不是大对象,.NET 运行时应该通过将所有内容移动到堆的开头来重新排列堆,然后释放已释放的内存部分。您确定您的分析器显示“持续”碎片问题,还是只是在快照时显示碎片(例如在运行时的碎片整理之间)?【参考方案2】:

在大对象堆上分配的对象(对象 >= 85,000 字节,通常是数组)不会被垃圾收集器压缩。微软认为移动这些对象的成本太高了。

建议尽可能重用大对象以避免 托管堆和 VM 空间上的碎片。

http://msdn.microsoft.com/en-us/magazine/cc534993.aspx

我假设您的大对象是由您的反序列化库创建的临时字节数组。如果库允许您提供自己的字节数组,您可以在程序开始时预先分配它们,然后重用它们。

【讨论】:

【参考方案3】:

有趣的是,它在 WebDevServer 上的工作方式与 IIS 不同...

是否有可能 IIS 使用服务器垃圾收集器,而 WebDev 服务器使用工作站垃圾收集器?垃圾收集的方法会影响碎片。它可能会在您的 aspnet.config 文件中设置。见:http://support.microsoft.com/kb/911716

【讨论】:

【参考方案4】:

一些测试和一些 C++ 之后,我找到了我获得这么多可用内存的原因,这是因为 IIS 通过 VM Hoarding 实例化 CLR(提供一个 dll 来实例化它而不使用 VM Hoarding 占用尽可能多的初始内存,但随着时间的推移确实会释放大部分内容,这是我期望的行为)。 所以这确实解决了我报告的内存问题,但是无论如何我仍然可以获得大约 100mb 的可用内存,我仍然认为这是由于碎片和碎片只被一次释放,因为探查器仍然报告内存碎片。因此,不要将我自己的答案标记为答案,希望有人能对此有所了解或指导我使用可以解决此问题或帮助我调试根本原因的工具。

【讨论】:

由于完全相同的原因,我遇到了完全相同的问题。尽管使用 WinDbg + SOS + SOSEX 来检查进程转储和“高级 .NET 调试”作为参考,但我还将其追踪到 VM 囤积(它实际上提到了囤积作为添加的 .net 2 功能)。那就是我现在被困住了。 我正在考虑的一种潜在补救措施可能是启动单独的进程以从数据库中预缓存数据(我们正在使用分布式缓存) 嗨,Ronan,我发现我的应用程序出现了完全相同的问题。你说虚拟机囤积是问题所在;你是如何为 IIS 关闭它的?或者您是如何以其他方式规避它的? 罗南,我和戴夫有同样的问题。您是如何为 IIS 关闭它的,或者甚至有可能吗?【参考方案5】:

如果您还没有找到答案,我认为以下线索可以帮助您:

回到基础:我们有时会忘记对象可以显式释放,显式调用对象的 Dispose 方法(因为你没有提到它,我想你做了一个“object = null" 指令)。

使用继承的方法,你不需要实现一个,除非你的类没有,我怀疑。

有关此方法的 MSDN 帮助说明:

... 实施 Dispose 没有性能优势 仅使用托管资源(例如数组)的类型的方法 因为它们会被垃圾收集器自动回收。采用 Dispose 方法主要用于使用本机的托管对象 资源和暴露给 .NET 的 COM 对象 框架。 ...

因为它说“它们被垃圾收集器自动回收”我们可以推断当方法被调用时会做“释放的事情”(我再次试图只是给你线索)。

此外,我还发现了这篇有趣的文章(我想……我没有读过……完全):垃圾收集:Microsoft .NET 框架中的自动内存管理 (http://msdn.microsoft.com/en-us/magazine/bb985010.aspx),它在“强制清理对象”部分:

...,还建议您添加一个附加方法 允许该类型的用户显式清理 在他们想要的时候反对。按照惯例,应该调用此方法 关闭或丢弃 ....

如果您仔细阅读或只是继续朝这个方向研究,也许答案就在这篇文章中。

【讨论】:

当一个对象被垃圾回收器(包括Dispose()或其他方式)释放时,回收的内存进入进程的“空闲空间”堆。那就是“蓝色”饼图。问题是关于将内存释放回操作系统以便其他进程可以使用它。【参考方案6】:

从 .NET 4.5.1 开始,您可以在调用 GC collect 之前设置一个一次性标志来压缩 LOH,即

Runtime.GCSettings.LargeObjectHeapCompactionMode = System.Runtime.GCLargeObjectHeapCompactionMode.CompactOnce; GC.Collect(); // 这将导致 LOH 被压缩(一次)。

【讨论】:

以上是关于.NET 可用内存使用情况(如何防止过度分配/释放内存给操作系统)的主要内容,如果未能解决你的问题,请参考以下文章

db2 内存总结

"每日一道面试题".net托管堆是否会存在内存泄漏的情况

Linux内核(linux-5.2.9)--内核对象(对象的引用计数)

oom killer

linux内存管理及手动释放机制

linux环境内存分配原理 mallocinfo