为什么后台垃圾收集有时会暂停我的应用程序,如何防止它?

时间:2014-08-06 11:00:34

标签: c# .net wpf garbage-collection

我们有一个大(=它可能需要200 - 500 MB或更多的内存在一天)WPF应用程序,每天使用多个小时。有时,应用程序挂起没有明显的原因。分析进程转储显示垃圾收集处于活动状态以及暂停应用程序的原因。

我们正在使用.NET 4.0,据我了解新推出的" background garbage collection"应该减少整个进程被阻止进行垃圾收集的时间(前台垃圾收集)。

然而,即使暂停不是经常发生,当他们花费超过几秒钟时就会中断工作流程(在这种情况下)。

这引出了以下问题:

  • 我读到在服务器环境中,默认情况下不使用后台垃圾回收。我们的应用程序确实在服务器操作系统(Windows Server 2003 R2 x64)上运行,尽管它是客户端应用程序(而不是服务器应用程序)。这是否意味着不使用后台垃圾收集?或者它只适用于服务/ ASP.NET?

  • 假设确实启用了后台垃圾收集,我该如何防止前景收集过于频繁/花费太长时间?我目前的方法是检测应用程序中的空闲时段(例如,应用程序未被使用5分钟或更长时间)并启动强制垃圾收集,以便在应用程序未来的时间段内不会发生这种情况。正在使用。

调试Diag进程转储信息: enter image description here

堆栈追踪:

mscorlib_ni!System.GC.Collect(Int32, System.GCCollectionMode)+47 
[[InlinedCallFrame] (System.GC._Collect)] System.GC._Collect(Int32, Int32) 
PresentationCore_ni!MS.Internal.MemoryPressure.ProcessAdd()+1d0 
PresentationCore_ni!MS.Internal.MemoryPressure.Add(Int64)+39 
PresentationCore_ni!System.Windows.Media.SafeMILHandleMemoryPressure..ctor(Int64)+43 
PresentationCore_ni!System.Windows.Media.SafeMILHandle.UpdateEstimatedSize(Int64)+38 
PresentationCore_ni!System.Windows.Media.Imaging.RenderTargetBitmap.FinalizeCreation()+df 
PresentationCore_ni!System.Windows.Media.Imaging.RenderTargetBitmap..ctor(Int32, Int32, Double, Double, System.Windows.Media.PixelFormat)+d9 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.HostUtils.GetRenderTargetBitmapForVisual(Int32, Int32, System.Windows.Media.Visual)+b1 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.HostUtils.GetBitmapForFrameworkElement(System.Windows.FrameworkElement)+89 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.HostUtils.GetBitmapForTransparentWindowsFormsHost(System.Windows.Forms.Integration.WindowsFormsHost)+4b 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.HostUtils.GetBitmapForWindowsFormsHost(System.Windows.Forms.Integration.WindowsFormsHost, System.Windows.Media.Brush)+1f 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.WindowsFormsHostPropertyMap.BackgroundPropertyTranslator(System.Object, System.String, System.Object)+109 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.PropertyMap.RunTranslator(System.Windows.Forms.Integration.PropertyTranslator, System.Object, System.String, System.Object)+32 
WindowsFormsIntegration_ni!System.Windows.Forms.Integration.WindowsFormsHost.ArrangeOverride(System.Windows.Size)+277 
PresentationFramework_ni!System.Windows.FrameworkElement.ArrangeCore(System.Windows.Rect)+8e3 
PresentationCore_ni!System.Windows.UIElement.Arrange(System.Windows.Rect)+385 
PresentationCore_ni!System.Windows.ContextLayoutManager.UpdateLayout()+2b5 
PresentationCore_ni!System.Windows.ContextLayoutManager.UpdateLayoutCallback(System.Object)+19 
PresentationCore_ni!System.Windows.Media.MediaContext+InvokeOnRenderCallback.DoWork()+10 
PresentationCore_ni!System.Windows.Media.MediaContext.FireInvokeOnRenderCallbacks()+76 
PresentationCore_ni!System.Windows.Media.MediaContext.RenderMessageHandlerCore(System.Object)+8a 
PresentationCore_ni!System.Windows.Media.MediaContext.AnimatedRenderMessageHandler(System.Object)+6e 
WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)+53 
WindowsBase_ni!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)+42 
WindowsBase_ni!System.Windows.Threading.DispatcherOperation.InvokeImpl()+8d 
WindowsBase_ni!System.Windows.Threading.DispatcherOperation.InvokeInSecurityContext(System.Object)+38 
mscorlib_ni!System.Threading.ExecutionContext.runTryCode(System.Object)+51 
[[HelperMethodFrame_PROTECTOBJ] (System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup)] System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode, CleanupCode, System.Object) 
mscorlib_ni!System.Threading.ExecutionContext.RunInternal(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+6a 
mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object, Boolean)+7e 
mscorlib_ni!System.Threading.ExecutionContext.Run(System.Threading.ExecutionContext, System.Threading.ContextCallback, System.Object)+2c 
WindowsBase_ni!System.Windows.Threading.DispatcherOperation.Invoke()+68 
WindowsBase_ni!System.Windows.Threading.Dispatcher.ProcessQueue()+15e 
WindowsBase_ni!System.Windows.Threading.Dispatcher.WndProcHook(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+63 
WindowsBase_ni!MS.Win32.HwndWrapper.WndProc(IntPtr, Int32, IntPtr, IntPtr, Boolean ByRef)+be 
WindowsBase_ni!MS.Win32.HwndSubclass.DispatcherCallbackOperation(System.Object)+7d 
WindowsBase_ni!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate, System.Object, Int32)+53 
WindowsBase_ni!MS.Internal.Threading.ExceptionFilterHelper.TryCatchWhen(System.Object, System.Delegate, System.Object, Int32, System.Delegate)+42 
WindowsBase_ni!System.Windows.Threading.Dispatcher.InvokeImpl(System.Windows.Threading.DispatcherPriority, System.TimeSpan, System.Delegate, System.Object, Int32)+b4 
WindowsBase_ni!MS.Win32.HwndSubclass.SubclassWndProc(IntPtr, Int32, IntPtr, IntPtr)+104 
WindowsBase_ni!DomainBoundILStubClass.IL_STUB_PInvoke(System.Windows.Interop.MSG ByRef)+3c 
[[InlinedCallFrame]] 
WindowsBase_ni!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame)+c1 
WindowsBase_ni!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame)+49 
PresentationFramework_ni!System.Windows.Application.RunDispatcher(System.Object)+5b 
PresentationFramework_ni!System.Windows.Application.RunInternal(System.Windows.Window)+74 
PresentationFramework_ni!System.Windows.Application.Run(System.Windows.Window)+2b 

1 个答案:

答案 0 :(得分:4)

您可以使用app.config更改GC模式。默认情况下,服务器Windows禁用后台GC。推理非常简单 - 后台GC意味着较少的可见延迟(最适合用户应用程序),而前景GC具有更好的总吞吐量。

但是,如果GC集合足够长以产生明显的挂起,那么您可能做错了什么。它并不是直接的内存量,它更可能是这种内存处理效率低下的结果。我见过的迄今为止最大的罪犯是手动使用GC.Collect - 这基本上会杀死世代GC(以及其他优化)的所有性能提升,这是非常重要的。 Debug Diag片段似乎表明事实确实如此 - 你似乎是在手动启动集合;但我从来没有使用过这个工具,所以这可能是误报。

200-500 MiB肯定不是很多。关键点在于收集的容易程度。这取决于对象如何分配给不同的世代,有多少对象(而不是它们的总大小,尽管这显然也起作用),内存局部性和许多其他事物。

有点违反直觉,在.NET中,强迫自己重用对象并像C ++一样进行类似的优化通常是一个坏主意。最有可能的是,它会导致更糟糕的GC性能,因为它会损害内存局部性和堆的分代。

关键点仍然是 - 个人资料。附加Concurrency Visualizer和CLRProfiler。他们会告诉你GC实际做了多少工作,并且可以帮助你理解原因。

并重申 - 不要使用GC.Collect。我从来没有见过它会导致性能提升,而且我已经看到它会多次杀死GC性能。我见过的唯一合理用例是基准测试 - 实际上很简单,可以从强制收集中获益。如果你不尝试那些微优化,那么堆实际上几乎和堆栈一样快(主要是在范围有限的对象上工作 - 一个方便的快捷方式)。 .NET的GC实际上非常好。

修改

根据其他信息,我担心在WPF应用程序中进行大量后台工作时,这可能是一个重大问题。如果将后台工作分成一个额外的进程,并且仅将WPF应用程序作为一个底层服务的GUI前端威胁,那么您可能会解决整个问题。显然,这不会很容易实现......

另一种选择是尝试尽可能地限制WPF垃圾 - WPF使用的数据越少,调用GC.Collect(2)的可能性就越小。

迫使GC在非服务器窗口上运行的app.config设置非常简单:

<configuration>
 <runtime>
  <gcServer enabled="false" />
  <gcConcurrent enabled="true" />
 </runtime>
</configuration>

如果您有多个处理器核心可用,它应该有助于缩短用户界面无响应的时间。