C# · 12月 26, 2021

c# – 即使内存可用,MemoryFailPoint总是会抛出一个InsufficientMemoryException

我写了下面的代码来检查是否有足够的内存,while (true){ try { // Check for available memory. memFailPoint = new MemoryFailPoint(250); break; } catch (InsufficientMemoryException ex) { if (memFailPoint != null) { memFailPoint.Dispose(); } Thread.Sleep(waitSecond * 1000); }}

我在Windows 7 64位机器上的控制台应用程序中运行上述操作.

这种方法每10秒钟有4个通话.

最初它工作正常,但2-3小时后总是有一个InsufficientMemoryException抛出.我检查可用的内存,它显示超过1 GB.

我尝试了很多,但是我无法找到为什么会发生这种情况.

以下是堆栈跟踪:

at System.Runtime.MemoryFailPoint..ctor(Int32 sizeInMegabytes)at SocketListner.AcceptConnection(IAsyncResult res) in H:\Projects\SocketListner.cs:line 308

没有内在的例外.

解决方法 您可以依靠这种方法正常工作,当您要求250兆字节时,这种异常很可能在32位进程中跳闸.当程序运行一段时间后,这很难得到.

程序永远不会与OOM崩溃,因为您已经消耗了所有可用的虚拟内存地址空间.它崩溃,因为地址空间中没有一个足够大的空间来适应分配.你的代码要求一个足够大的洞,在一个gulp中分配250兆字节.当您没有得到异常时,您可以确保此分配不会失败.

但是250兆字节相当多,这是一个非常大的阵列.并且由于称为“地址空间碎片”的问题很可能会失败.换句话说,程序通常开始有几个非常大的漏洞,最大的是大约600兆字节.在存储代码和由.NET运行时使用的数据和非托管Windows DLL之间进行的分配之间可用的孔.随着程序分配更多的内存,这些孔变小.它可能释放一些记忆,但不会重现一个大洞.你通常会得到两个孔,大约是原来大小的一半,中间的位置分成两个原来的大洞.

这被称为碎片,一个32位进程,分配和释放大量的内存,最终分裂虚拟内存地址空间,所以在一段时间之后仍然可用的最大的漏洞大概在90 MB左右.要求250兆字节几乎保证失败.你需要瞄准更低.

您无疑期望它的工作方式不同,确保总计250兆字节的分配总和得到保证.然而,这不是MemoryFailPoint如何工作,它只检查最大可能的分配.也许不用说,这使得它不太有用.否则我们会同情.NET框架程序员,让它按照我们想要的方式工作,既昂贵又不能实际提供保证,因为分配的大小最重要.

虚拟内存是一个非常便宜的丰富资源.但接近消费这一切都是非常麻烦的.一旦你消耗了一个千兆字节,那么随机的OOM开始变得可能.不要忘记这个问题的简单修复,你正在64位操作系统上运行.所以只需将EXE平台目标更改为Anycpu即可获取虚拟地址空间的gobs和gob.取决于操作系统版本,但可能是一TB.它仍然碎片,但你只是不在乎,孔是巨大的.

最后但并非最不重要的是,在注释中可以看到,这个问题与RAM无关.虚拟内存与你有多少RAM是无关的.将虚拟内存地址映射到RAM中的物理地址是操作系统的工作,它动态地执行.访问内存位置可能会跳过页面错误,OS将为页面分配RAM.反之亦然,当其他地方需要时,操作系统将取消映射RAM的RAM.你永远不会用尽RAM,机器会慢慢爬行,才能发生. SysInternals的VMMap实用程序很高兴看到您的程序的虚拟地址空间是什么样的,尽管您倾向于将信息淹没在一个大的进程中.