我们在开发过程中曾经遇到过一个奇怪的问题:当软件加载了很多比较大规模的数据后,会偶尔出现OutOfMemoryException异常,但通过内存检查工具却发现还有很多可用内存。于是我们怀疑是可用内存总量充足,但却没有足够的连续内存了——也就是说存在很多未分配的内存空隙。但不是说.NET运行时的垃圾收集器会压缩使用中的内存,从而使已经释放的内存空隙连成一片吗?于是我深入研究了一下垃圾回收相关的内容,最终明确的了问题所在——大对象堆(LOH)的使用。如果你也遇到过类似的问题或者对相关的细节有兴趣的话,就继续读读吧。
如果没有特殊说明,后面的叙述都是针对32位系统。
首先我们来探讨另外一个问题:不考虑非托管内存的使用,在最坏情况下,当系统出现OutOfMemoryException异常时,有效的内存(程序中有GC Root的对象所占用的内存)使用量会是多大呢?2G? 1G? 500M? 50M?或者更小(是不是以为我在开玩笑)?来看下面这段代码(参考 https://www.simple-talk.com/dotnet/.net-framework/the-dangers-of-the-large-object-heap/)。
public class Program { static void Main(string[] args) { var smallBlockSize = 90000; var largeBlockSize = 1 << 24; var count = 0; var bigBlock = new byte[0]; try { var smallBlocks = new List<byte[]>(); while (true) { GC.Collect(); bigBlock = new byte[largeBlockSize]; largeBlockSize++; smallBlocks.Add(new byte[smallBlockSize]); count++; } } catch (OutOfMemoryException) { bigBlock = null; GC.Collect(); Console.WriteLine("{0} Mb allocated", (count * smallBlockSize) / (1024 * 1024)); } Console.ReadLine(); } }
这段代码不断的交替分配一个较小的数组和一个较大的数组,其中较小数组的大小为90, 000字节,而较大数组的大小从16M字节开始,每次增加一个字节。如代码第15行所示,在每一次循环中bigBlock都会引用新分配的大数组,从而使之前的大数组变成可以被垃圾回收的对象。在发生OutOfMemoryException时,实际上代码会有count个小数组和一个大小为 16M + count 的大数组处于有效状态。最后代码输出了异常发生时小数组所占用的内存总量。
下面是在我的机器上的运行结果——和你的预测有多大差别?提醒一下,如果你要亲自测试这段代码,而你的机器是64位的话,一定要把生成目标改为x86。
23 Mb allocated
考虑到32位程序有2G的可用内存,这里实现的使用率只有1%!
下面即介绍个中原因。需要说明的是,我只是想以最简单的方式阐明问题,所以有些语言可能并不精确,可以参考http://msdn.microsoft.com/en-us/magazine/cc534993.aspx以获得更详细的说明。
.NET的垃圾回收机制基于“Generation”的概念,并且一共有G0, G1, G2三个Generation。一般情况下,每个新创建的对象都属于于G0,对象每经历一次垃圾回收过程而未被回收时,就会进入下一个Generation(G0 -> G1 -> G2),但如果对象已经处于G2,则它仍然会处于G2中。
软件开始运行时,运行时会为每一个Generation预留一块连续的内存(这样说并不严格,但不影响此问题的描述),同时会保持一个指向此内存区域中尚未使用部分的指针P,当需要为对象分配空间时,直接返回P所在的地址,并将P做相应的调整即可,如下图所示。【顺便说一句,也正是因为这一技术,在.NET中创建一个对象要比在C或C++的堆中创建对象要快很多——当然,是在后者不使用额外的内存管理模块的情况下。】
以上是小编为您精心准备的的内容,在的博客、问答、公众号、人物、课程等栏目也有的相关内容,欢迎继续使用右上角搜索按钮进行搜索内存
, 问题
, 五连珠问题
, 对象
, 中创
, outofmemoryexception
, 收集器
一个
中等收入陷阱的对策、hacknet 陷阱、hacknet shell陷阱、.net json 转对象、asp.net内置对象,以便于您获取更多的相关知识。