如何使.NET进程耗尽内存而不会耗尽所有系统内存

6
问题很简单,我有一个处理程序,可以对一些XML文件进行ETL。我们开始收到非常大的XML文件,并且我开始收到OutOfMemoryExceptions异常。
修复过程相对简单。然而,我想为我的NUnit套件编写一个单元测试,以确保该进程将继续能够处理非常大的文件。但是,在我的开发工作站上实际耗尽内存会使我的机器变慢,并且很耗时间。在版本控制中存储一个巨大的测试文件也是不好的主意。如果我可以人为地限制进程、线程或应用程序域只使用固定量的RAM,比如128兆字节,我可以制作一个更小的单元测试,不会让我的工作站崩溃。
有什么建议吗?是否有一些未经管理的API可以P/Invoke?

我不想把这个放在我的主要答案中,因为它并没有完全回答你的问题,但这听起来像是一个奇怪的单元测试。从高层次上讲,无论是读取文件的算法处理任意大小的非常大的文件(因为它流式传输或分块文件),还是不处理。这不会经常改变,如果有的话,我不明白你每次用测试套件测试它能获得什么好处,特别是考虑到(如果失败)它可能不会在给定的文件上每次可靠地失败。 - mqp
我会多次打开和扫描文件。有时我会使用流,有时不会。我扫描的99.9%的文件都足够小,可以将整个文件加载到内存中。另外的0.01%需要处理。因此,如果我将所有文件操作都基于流(修复问题),那么可能会有人添加一个新步骤,通过将其全部加载到内存中来处理文件。如果没有一个单元测试在足够大的文件上执行整个ETL操作,以占用所有处理内存,这将进入生产环境,因为QA可能不会使用真正大的文件测试系统。 - Justin Dearing
我认为这里最好的答案是非常好地封装您需要执行的操作,以便其他人很难意外地走下获取文件句柄并打开整个文件来尝试获取某些内容的路线。当然,如果有人足够努力,仍然可以穿过您的丛林打开整个文件,但我认为您可以通过纯粹的无知防止任何人这样做。(我知道这与如何测试它的讨论无关,但预防胜于治疗。) - mqp
从另一个角度看待这个问题——你如何限制 .net 进程会占用的内存量?目前,.net 进程可以访问整个系统,但 CLR 应该能够跟踪已分配的内存量并在消耗所有物理和虚拟内存之前开始拒绝请求。在 Java 中,可以使用 JRE 的 -Xmx<size> 参数来实现这一点。在 .net 中,似乎没有相应的方法。 - Michael Donohue
4个回答

2

你不能使用模拟框架来进行内存分配并将其作为一个测试抛出OutOfMemoryException吗?

话虽如此,如果你真的已经用完了内存,那么你的应用程序就没有太多安全的操作了,但是如果你可以至少优雅地失败,你的用户会感激你。

例如: 我曾经在一份工作中遇到过这样一种情况,我们实时显示工厂的3D模型。当我们尝试加载纹理时,模型变得非常大,我们会遇到内存不足的错误。我们通过确保代码处理空指针来保持应用程序的运行和渲染,即使其他代码认为应该存在纹理信息。


我正打算就模拟对象提出同样的建议。 - RichardOD

1

使用Mocking进行单元测试是最好的。实际上,通过抛出OOM异常来进行单元测试是不符合定义的。当处理内存时,您正在处理负载测试。如果您阅读此电子邮件底部的链接,您将发现,在最好的情况下,真正的OOM异常很难重现和调试。人为制造的OOM异常并不是异常的真正原因,因此不比用于测试的模拟对象更有趣。

坚持使用Mock进行单元测试。如果仍然遇到OOM异常,请在服务器上增加更多内存,并更经常地使进程重启/重新启动。

以下是我上一次与OOM异常斗争时收集的一些有趣阅读材料。总结:OOM异常发生在系统无法分配您请求的数量时 - 这并不意味着您的内存已用尽。


如果你仍然遇到OOM(内存不足)问题,可以在服务器上增加更多的内存,并且更频繁地进行进程回收/重启。增加内存并不能解决问题...他的进程已经用完了虚拟内存。物理内存不是问题所在。假设你正在运行32位平台,那么你可以从堆和栈中分配2GB的应用程序地址空间。当你无法在地址空间中分配足够的连续内存时,就会出现OOM错误。 - Brian ONeil

0

在进程中很容易引起内存不足异常。

只需创建一个循环,该循环以足够小而不会在大对象堆上的块分配内存(但不要太多,否则会导致异常),然后您可以尝试打开一个较小的文件,这将导致无法分配足够连续的内存来打开该文件,当您打开文件时,将出现OOM异常,而无需巨大的文件。就像这样...

List<byte[]> items = new List<byte[]>();
for (int i = 0; i < 10000; i++)
{
     byte[] c = new byte[160000];
     items.Add(c);
}

byte[] next = new byte[1000000000];

如果您按原样运行上面的代码,最后一行将会出现OOM异常。但是,如果您首先注释掉循环,它将不会出现任何错误。您可能需要微调循环以使其每次都无法打开您的文件,但您可以做到。只需在测试中调用打开文件之前运行循环,您就已经使用了大量内存,而且打开应该会失败。
此外,如果您有这个选项,您可能还想考虑设置/3GB开关。这并不总是正确的答案,并且它也有一些缺点,但它将虚拟内存分割从2GB/2GB更改为1GB/3GB,允许您的进程访问更多的虚拟地址空间。这将为您打开文件的大小提供更多的余地。同样,在尝试将其作为解决方案之前,您应该阅读其缺点,并确保它是否值得为您的情况提供帮助。 此处是如何在服务器上启用它的方法。

0

我不太明白你在这里想说什么。假设你以某种方式创建了一个人工小的“测试环境”,无法分配更大的内存块,因此会对较小的文件抛出OutOfMemoryExceptions异常。你获得了什么?单元测试旨在测试您是否可以在实际系统上处理更大的文件,对吧?

重要的是,您可以处理任何需要的大小的文件,在它们将在其上运行的任何系统上,除了尝试在该系统上的该文件之外,没有真正的方法来对其进行单元测试。

(较小但不那么重要的事情可能是您是否优雅地处理OutOfMemoryException,但您无需真正耗尽内存即可测试它;只需使您的方法偶尔抛出异常并观察它是否执行正确操作即可。)


测试的目的是系统能够处理任意大的文件,而不仅仅是大文件。换句话说,所有的文件操作都可以在不将整个文件加载到内存中的情况下完成,并且系统应该没有处理文件大小的限制。 - Justin Dearing

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接