- A+
通过代码实现 OutOfMemory
Intro
来尝试写一个发生 OutOfMemoryException
的代码吧,开启煞笔代码第三篇 —— OutofMemory
OutOfMemory
OutOfMemory
顾名思义就是内存不足,在 .NET 中当内存不足的时候就会抛出 OutOfMemoryException
的异常。
想要触发 OutOfMemoryException
就要满足内存不足的条件,在 .NET Framework 中可能就只能一直分配内存直到内存不足,再没有足够的内存可以分配了,在 .NET Core 3.x 版本以后,微软引入了一些 GC 的配置,我们可以通过这些配置来指定最大的 GC 内存,这样我们就可以实现触发 OutOfMemoryException
而不影响其他应用程序正常运行的目标了。在 .NET 5 中我们又可以更进一步更精细的控制 GC 使用的内存了,在 .NET 5 中我们可以针对每个堆(SOH/LOH/POH)来设置内存限制。
GC 堆内存限制配置
我们测试的示例使用限制 GC 堆大小 (Heap Limit) 的方式来限制应用程序的内存占用以免影响到别的应用程序正常运行(该配置只针对 64 位电脑有效,现在的电脑应该大多都是64位吧)。
配置的方式有两种,一种是通过环境变量来配置,一种是通过 runtime.config.json
来配置
通过环境变量配置 COMPlus_GCHeapHardLimit
为要配置的内存大小,需要注意的是通过环境变量配置的时候指定的值需要是十六进制的值,通过 runtimeconfig.json
配置的时候是直接用十进制的数值
因为我们只是想简单的测试一下,不能影响别的应用程序,而且不能在代码里配置当前进程的环境变量,因为进程启动的时候 GC 的配置就已经加载好了,在代码里配置当前进程的环境变量来改变 GC 配置是不会生效的,所以我们选择配置 runtimeconfig.json
来测试,在项目的 bin
目录下可以找到 runtimeconfig.json
文件,我们修改这一个文件即可(使用 runtimeconfig.json
的时候需要注意先生成一下,然后再更新 runtimeconfig.json
文件)
测试配置如下,配置的 GC 堆的最大值是 1M(配置的不能太小,太小的话 CoreCLR 可能都会启动失败从而导致程序无法正常运行):
{ "runtimeOptions": { "tfm": "netcoreapp3.1", "framework": { "name": "Microsoft.NETCore.App", "version": "3.1.0" }, "configProperties": { "System.GC.HeapHardLimit": 1048576 } } }
测试代码
测试代码如下:
Console.ReadLine(); var bytes = GC.GetTotalAllocatedBytes(); Console.WriteLine($"AllocatedBytes: { bytes } bytes"); var list = new List<byte[]>(); try { while (true) { list.Add(new byte[85000]); } } catch (OutOfMemoryException) { Console.WriteLine(nameof(OutOfMemoryException)); Console.WriteLine(list.Count); bytes = GC.GetTotalAllocatedBytes(); Console.WriteLine($"AllocatedBytes: { bytes } bytes"); } Console.ReadLine();
测试输出如下:
上面的测试代码使用的 byte 数组的长度是 85000 的原因是,当要分配的对象大于等于 85k(85000)时会直接分配到大对象堆中,正好可以测试一下。
我们使用微软的 dotnet dump 诊断工具来测试一下
第一次 dump 是在 list 对象创建之前进行的,第二次 dump 是发生 OutOfMemory
之后的
从上面的 dump 结果可以看的出来,byte 数组的对象确实是分配在大对象堆(LOH)上的,几乎所有的内存分配都在大对象堆中,有一些小对象从0 代升到了 1代。
More
上面的测试代码使用的 byte 数组的长度是 85000 ,你测试的时候也可以使用更大的值,或者直接使用 int.MaxValue
在前面的 StackOverflow
文章中,有网友评论说,他们之前遇到的一个 StackOverflow
示例常常伴随着 OutOfMemory
,递归和这种方式有点类似,都是要一直创建新的对象,分配新的内存。
除此之外,还有哪些更简单的方式吗?欢迎补充