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

4
我们有一个大型的WPF应用程序,每天使用多个小时,可能需要200-500 MB或更多的内存。偶尔,应用程序会无缘无故地挂起。分析进程转储显示垃圾收集正在进行,并且是暂停应用程序的原因。
我们使用.NET 4.0,并且据我所知,新引入的“background garbage collection”应该减少整个进程被垃圾收集(前台垃圾收集)阻塞的时间。
然而,即使这些暂停不那么频繁,当它们超过几秒钟时(这是情况),它们也会打断工作流程。
这引导我提出以下问题:
  • 我读到在服务器环境中,默认情况下不使用后台垃圾回收。我们的应用程序确实在服务器操作系统上运行(Windows Server 2003 R2 x64),尽管它是客户端应用程序(而不是服务器应用程序)。这是否意味着不使用后台垃圾回收?或者它只适用于服务/ASP.NET?

  • 假设确实启用了后台垃圾回收,如何防止前台回收发生得太频繁/时间过长?我的当前方法是检测应用程序的空闲期间(例如,应用程序未使用5分钟或更长时间),并启动强制垃圾回收,以便在应用程序被使用时不会在以后的某个时间发生。

Debug 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
你有什么证据支持你的说法,即“垃圾回收有时会暂停我的应用程序”?垃圾回收在 UI 线程上运行的可能性极小。 - Sheridan
1
你可能需要 .Net 4.5:http://msdn.microsoft.com/library/ee787088%28v=vs.110%29.aspx#background_server_garbage_collection - Matthew Watson
1
你第二个点的策略,即在应用程序中管理垃圾收集,是一个很棘手的问题。相反,考虑使用云技术或其他分布式技术来卸载一些处理任务。 - Gayot Fow
1
更糟糕的是,MemoryPressure.Add(一个 WPF 方法,而不是 GC.AddMemoryPressure)明确调用了 GC.Collect(2) - http://referencesource.microsoft.com/#PresentationCore/src/Core/CSharp/MS/Internal/MemoryPressure.cs。哎呀。可能在仅使用 WPF 和 GUI 时效果很好,但一旦开始在后台进行一些严肃的工作,它就会变得致命。 - Luaan
1
两点说明:我也有一个WinFormsHost,所以我一定会检查它是否导致了我的速度变慢。另外,你的WinFormsHost是否有透明的背景?将其更改为不透明是否可以解决问题?也许如果你可以防止从GetBitmapForTransparentWindowsFormsHost向下的链,并且在视图的布局期间避免GC,问题就会减轻。 - cod3monk3y
显示剩余22条评论
1个回答

4
您可以通过修改 app.config 文件来更改 GC 模式。默认情况下,服务器 Windows 禁用后台 GC。原因很简单 - 后台 GC 可以减少可见延迟(最适合用户应用程序),而前台 GC 具有更好的总吞吐量。
然而,如果 GC 收集时间足够长以产生明显的停顿,则可能是您做错了什么。这实际上与内存量无关,更可能是由于处理此类内存不高效造成的。我看到的最大的罪犯是手动使用 GC.Collect - 这基本上会杀死分代 GC(以及其他优化)的所有性能增益,这是非常重要的。Debug Diag 片段似乎表明这确实是情况 - 您似乎正在手动启动收集; 但我从未使用过该工具,因此可能是错误的阳性。
200-500 MiB 当然不多。关键点是收集有多容易。这取决于对象如何分配到不同的代中,有多少对象(而不是它们的总大小,尽管这显然也起到作用),内存局部性和许多其他因素。
有些违反直觉的是,在 .NET 中通常不强制自己重复使用对象并执行类似 C++ 的优化。最有可能导致更糟的 GC 性能,因为它会损害内存局部性和堆的分代分区。
关键点仍然是 - 进行分析。附加并发可视化器和 CLRProfiler。它们将告诉您 GC 实际上正在做多少工作,并帮助您了解原因。
再次强调 - 不要使用 GC.Collect。我从未见过它导致性能改进,但我多次看到它杀死 GC 性能。唯一合理的用例是基准测试 - 它们实际上足够简单,可以从强制收集中受益。如果您不尝试这些微观优化,则堆实际上可以几乎与堆栈一样快(通过大多数工作于范围限定对象 - 一个方便的快捷方式)。.NET 的 GC 实际上非常好。
编辑:
基于其他信息,恐怕在 WPF 应用程序中进行大量后台工作时,这可能实际上是一个重大问题。如果将后台工作分离到额外的进程中,并将 WPF 应用程序视为某些底层服务的 GUI 前端,则可能会避免整个问题。显然,这不会很容易实现...
另一个选择是尽可能限制 WPF 垃圾 - WPF 处理的数据越少,它就越不倾向于调用其 GC.Collect(2)
强制 GC 表现为非服务器 Windows 的 app.config 设置非常简单:
<configuration>
 <runtime>
  <gcServer enabled="false" />
  <gcConcurrent enabled="true" />
 </runtime>
</configuration>

如果您有多个处理器核心可用,它应该有助于减少UI无响应的时间。


@floele 你确定吗?我认为“自然”集合中没有GC.Collect。至少在进程转储/并发可视化器/Visual Studio调试器中,我从未看到过它。 - Luaan
非常确定 ;) 我现在附上了堆栈跟踪,这样你就可以自己看看了。 - floele
"服务器Windows已禁用后台GC" - 这适用于在服务器上运行的所有.NET应用程序吗?您是否有一个确切记录该行为的参考文献? - floele
1
@floele 你不需要相信我的话 - 检查 GCSettings.IsServerGCGCSettings.LatencyMode,它会告诉你你实际上正在运行哪个 GC。而且,堆栈确实表明了这是作为非托管内存分配的一部分发生的,而不是托管内存。这是 WPF 垃圾回收给你带来麻烦。这可能非常棘手,因为 WPF 明确调用了 GC.Collect(2),如果除了几个表单和图像之外还有其他数据,那么这将是可怕的 - Luaan
你有什么“微调”想法吗?我从未尝试过更改GC配置,不确定哪些设置应该在这种情况下帮助我。 - floele
显示剩余4条评论

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