若是不能彻底避免大对象堆的分配,则要尽可能避免碎片化。
对于LOH不当心就会有无限增加,但LOH使用的空闲列表机制能够减轻增加的影响。利用这个空闲列表,咱们能够在两块分配区域中间找到你所想要的可分配区域。
要作到这一点,就须要保证你在LOH里的分配都按照同一个尺寸或者同一个尺寸的倍数进行。例如,一个常见的需求是在LOH里分配缓冲区。要确保分配的每一个缓冲区都是一个大小,或者是一个知名数字(1M)的倍数,而不要建立大小不一的缓冲区。这样作的话,若是一个缓冲区被回收,那么下一个缓冲区在分配的时候,很大几率不会在堆结尾分配,而是会在被回收的地方从新分配。算法
继续用前面的MemoryStreams的的故事。咱们的第一个实现咱们只对PooledMemoryStream进行的池化,它的缓冲区增加仍是沿用MemoryStreams的默认算法,当超过容量是,会按照当前的缓冲区大小加倍申请。这虽然解决分配问题,可是又形成了碎片问题。第二次迭代的时候,咱们抛弃了这种申请算法,咱们倾向于实现一个流的抽象类,将多个128K直接的缓冲区合并使用,将这些小的缓冲区用连接的方式组成一个大的缓冲区,他们大小为1MB的倍数(最大为8MB)。这个新的实现大大减小了咱们的碎片问题,固然咱们偶尔还会不得不将一些128KB的数据复制到1MB的缓冲区里,但这样的改进也是值得的。缓存
在几乎全部的正常状况下,你是不该该主动执行完整GC操做的,这可能会打乱GC的自动处理流程,致使一些很差的结果。可是,在一些高性能系统里存在一些状况,咱们仍是会建议你进行一次完整GC。
一般,在有合适的时间窗口下进行完整GC,能够避免在从此很差的时间段执行GC。注意,这里讨论的只是耗时比较多完整GC,对于0代和1代的回收仍是应该频繁出发,以免构建的0代内存区太大。服务器
在下面状况能够作一次完整的完整GC:app
你若是使用了低延迟模式,在这种模式下,堆的大小会一直增加,这个时候你须要在合适的时间点来执行一次完成GC。oop
若是会偶尔大量分配一些长生命周期的对象(初始化对象池),在对象建立后,能够执行一次完整GC,将对象尽快转为2代对象。或者当你再也不使用这些对象,也最好在删除引用后强制回收他们。性能
若是你如今所处的状态,由于碎片太多,必需要作大对象堆作压缩的时候。测试
对于状况1,2都是在特定时间里经过强制执行GC来避免在不合适的时机被执行GC。状况3,若是你在LOH里有很大的碎片,则能够帮助你减小堆的大小。若是不是上面的状况,你最好另外想一些其它优化方案。优化
要执行完整GC,可使用GC.Collect来回收所但愿的代纪。还能够经过GCCollectionMode的枚举参数告诉GC是否当即执行。参数有3个值
Default--(默认)当前,强制
Forced--(强制)告诉GC当即开始收集
Optimized--(优化)由GC决定如今是不是要的时机执行回收this
GC.Collect(2); // 等价于 GC.Collect(2, GCCollectionMode.Forced);
即便使用了对象池,仍然可能会在大对象堆里分配对象,随着时间的推移,在里面会存在不少碎片。从.NET 4.5.1 开始,你能够告诉GC在下一次作完整GC时顺便也对LOH作一次压缩。线程
GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce;
根据LOH的大小,这个压缩过程可能会很慢,甚至会用到好几秒。你最好是在你的程序可以长时间暂停的时候,才让垃圾回收器作一次这样的完整GC。修改该设置值,只会在下一次完整GC时会触发压缩,一旦完成了LOH的压缩,GCSettings.LargeObjectHeapCompactionMode就会被从新设置为GCLargeObjectHeapCompactionMode.Default。
由于这个过程很耗时,我仍是建议你减小对LOH的分配或者使用对象池。这样将大大减小压缩的数据。压缩LOH功能只能做为碎片过多,分配的堆太大时的最后手段。
若是你的应用彻底不但愿受到2代的的GC影响,你能够在GC快来临前收到一个通知。这样能够给你一个机会,暂停现有的业务处理,将请求分流到其它服务器,或者进入某种对你更合理的状态。
可是我建议你谨慎使用,这个GC通知机制可能会给你产生一些意料以外的状况。你应该在全部的优化手段都使用后才考虑它。若是你有下面的状况,你能够利用GC通知功能。
2代的回收起始不多发生,更多的时候是在不少0代小对象分配时会达到触发的阈值,因此在收到GC的通知时,你还有不少工做须要作。
不幸的是,因为GC通知触发的不精确性,你只能在1-99范围你指定一个合适的触发时机。若是数字比较小,你可能会在里真正GC前才会收到消息,没有足够的时间作相应处理。但若是你的数字过高,这可能会被频繁触发而不会触及真正的GC。这两个选择取决你当前内存的分配率与内存负债。注意,这里会指定2个阈值数字,一个用于2代对象,一个用于LOH。与其它功能同样,GC会尽最大努力给你通知,但它不会保证你能不作此次GC。
要使用此功能,请按照一下步骤进行。
由于通知须要一个轮询的机制,你须要有一个线程按期的检查状态。若是你的程序里已经有这样的定时检查功能,你能够将它嵌入到检查流程里。固然也能够单独为GC检查建立一个独立的线程。
下面的是一个 GCNotification 的完整例子。它会不断的分配内存用来测试通知过程。
internal class Program { private static void Main(string[] args) { const int ArrSize = 1024; var arrays = new List<byte[]>(); GC.RegisterForFullGCNotification(25, 25); // Start a separate thread to wait for GC notifications Task.Run(() => WaitForGCThread(null)); Console.WriteLine("Press any key to exit"); while (!Console.KeyAvailable) { try { arrays.Add(new byte[ArrSize]); } catch (OutOfMemoryException) { Console.WriteLine("OutOfMemoryException!"); arrays.Clear(); } } GC.CancelFullGCNotification(); } private static void WaitForGCThread(object arg) { const int MaxWaitMs = 10000; while (true) { // There is also an overload of WaitForFullGCApproach // that waits indefinitely GCNotificationStatus status = GC.WaitForFullGCApproach(MaxWaitMs); bool didCollect = false; switch (status) { case GCNotificationStatus.Succeeded: Console.WriteLine("GC approaching!"); Console.WriteLine("-- redirect processing to another machine -- "); didCollect = true; GC.Collect(); break; case GCNotificationStatus.Canceled: Console.WriteLine("GC Notification was canceled"); break; case GCNotificationStatus.Timeout: Console.WriteLine("GC notification timed out"); break; } if (didCollect) { do { status = GC.WaitForFullGCComplete(MaxWaitMs); switch (status) { case GCNotificationStatus.Succeeded: Console.WriteLine("GC completed"); Console.WriteLine("-- accept processing on this machine again --"); break; case GCNotificationStatus.Canceled: Console.WriteLine("GC Notification was canceled"); break; case GCNotificationStatus.Timeout: Console.WriteLine("GC completion notification timed out"); break; } // Looping isn't necessary, but it's useful if you want // to check other state before waiting again. } while (status == GCNotificationStatus.Timeout); } } } }
另一种触发方式是压缩LOH堆,可是基于内存使用触发更合适一些。
被弱引用对象引用的对象时能够在GC的时候被回收的。这与强引用造成对别,强引用后的对象是不会被回收的。弱引用主要用来缓存你想保留的不是很重要的对象,一旦应用有内存上的压力,就有可能被回收。
WeakReference weakRef = new WeakReference(myExpensiveObject); … // Create a strong reference to the object, // now no longer eligible for GC var myObject = weakRef.Target; if (myObject != null) { myObject.DoSomethingAwesome(); }