【问题标题】:Log WinForms application state记录 WinForms 应用程序状态
【发布时间】:2018-11-28 10:52:38
【问题描述】:

我们有一个 WinForms 应用程序,它偶尔会抛出 OutOfMemory 异常。我们无法在任何开发机器上产生这个错误,但它偶尔会发生在我们的一些客户身上。我确信这不是由内存泄漏引起的,而是应用程序试图处理太多数据。这是有道理的,因为只有少数最大的客户遇到了这个问题。然而,问题是我不知道发生这种情况时他们在做什么。我们尝试过询问他们,答案类似于“它只是偶尔发生。”。我们有一个异常报告服务,它记录生产中发生的所有未处理的异常,但由于内存不足的工作原理,罪魁祸首不一定在调用堆栈中。对于这些问题,我从未在调用堆栈中看到我们的任何代码。

有没有办法在抛出这些异常时确定应用程序中发生了什么?例如打开了哪些窗口以及在这些窗口的控件中显示了哪些(多少)信息等。

以下是一些示例调用堆栈:

消息:引发了“System.OutOfMemoryException”类型的异常。 System.Threading.WaitHandle.WaitAny(WaitHandle[] waitHandles, Int32 毫秒超时,布尔退出上下文) System.Net.TimerThread.ThreadProc():436 System.Threading.ThreadHelper.ThreadStart_Context(对象状态):20 System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext、ContextCallback 回调、对象状态、布尔值 保留SyncCtx):112 System.Threading.ExecutionContext.Run(ExecutionContext executionContext、ContextCallback 回调、对象状态、布尔值 保留SyncCtx):0 System.Threading.ExecutionContext.Run(ExecutionContext executionContext、ContextCallback 回调、对象状态):43 System.Threading.ThreadHelper.ThreadStart():8

--

消息:引发了“System.OutOfMemoryException”类型的异常。 System.Reflection.RuntimeAssembly.GetCodeBase(RuntimeAssembly 程序集,布尔复制名称,StringHandleOnStack retString):-1 System.Reflection.RuntimeAssembly.GetCodeBase(布尔复制名称):0 System.Reflection.RuntimeAssembly.GetName(布尔复制名称):0 System.Reflection.Assembly.GetName():0 System.Windows.Forms.ThreadExceptionDialog..ctor(异常 t):1033 System.Windows.Forms.Application+ThreadContext.OnThreadException(异常 t):57 System.Windows.Forms.Control.WndProcException(异常 e):0 System.Windows.Forms.Control+ControlNativeWindow.OnThreadException(异常 e):0 System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 味精,IntPtr wparam,IntPtr lparam):58

【问题讨论】:

  • 您可以设置procdump 在客户机器上作为调试器运行,并编写崩溃进程的完整内存转储。然后,您可以将此转储加载到开发计算机上的 Visual Studio 中,并可以使用 Visual Studio 调试器检查应用程序的状态。
  • 大客户? - 他们使用虚拟机吗?与物理 PC 相比,它们通常没有设置太多内存。询问他们正在使用什么配置,并尝试在您自己的虚拟机中模拟类似的东西,尽管您永远不会完全复制他们的黄金构建虚拟机。只是一个想法
  • 根据我的经验,.NET 中的 OutOfMemory 异常甚至与内存无关。它们是失败的 COM 或 GDI 调用。显然,返回的错误代码是 .NET 无法理解的,因此它使用 OutOfMemory 异常。
  • OOM 的一个重要原因是代码分配了 lot 的临时对象,内存碎片严重到内存分配器无法找到足够的空间来分配新对象.例如,列表将项目存储在数组中,每次满时都必须重新分配。例如,将 1M 项逐个添加到列表中将导致 20 次重新分配,使用 2M*type-size 字节。一个 1M 项目 List 逐项构造将消耗 8MB。创建一个足够大的capacity 列表将显着减少重新分配。
  • @PanagiotisKanavos 没错,但如果这是问题的根源,我们的代码不应该出现在调用堆栈中吗?

标签: c# .net winforms out-of-memory


【解决方案1】:

这里可以找到关于这不是什么的最佳解释

https://blogs.msdn.microsoft.com/ericlippert/2009/06/08/out-of-memory-does-not-refer-to-physical-memory/

基本思想是,在现代 Windows 操作系统中,计算机会在内存不足之前死机。它需要消耗硬盘的每一个字节,而且它会交换得如此糟糕,以至于在内存实际耗尽之前很久就会注意到可怕的减速。

那么,什么是“OutOfMemoryException”,为什么会发生?嗯,有一些原因,有些与记忆有关,有些原因与记忆完全无关。

此错误的最常见来源:使用错误参数的 GDI+ 调用。来自 GDI+ 的任何错误,包括使用 System.Drawing 库,都会引发 OutOfMemoryException。这似乎是因为底层 API 没有向 .NET 提供有关失败原因的任何信息,因此 .NET 的创建者决定将它们全部称为 OutofMemoryException,现在他们坚持使用它以实现兼容性。尝试创建 0 号笔或创建带有错误格式标志的位图会导致“内存不足”

.NET 可能将 OutOfMemory 用于 GDI 错误的原因是很多错误实际上与内存有关。仍然有数量有限的 GDI 句柄可用。它可能是几百万,但仍然有限,所以如果有东西泄漏了底层 HBITMAP 或 HBRUSH 或 HPEN,最终它会用完,GDI 调用失败,你会得到一个异常。

我看到错误的另一个地方是使用任何 COM 接口。如果 COM 对象返回错误,您可能会收到 OutOfMemory 异常。我尝试捕捉并处理这些错误。

要进行跟踪,请查看与 GDI/System.Drawing 相关的调用。查看堆栈跟踪,它向您显示了问题。

此外,不要假设“OutOfMemory”错误需要关闭应用程序。您应该能够从这些异常中恢复得很好,因为您并没有真正的内存不足。为什么客户甚至会看到此错误?

【讨论】:

  • 最后一句话是我想要找出的。该应用程序是我们主应用程序的管理工具,主要用于查看和编辑数据库。我最初的想法是从数据库中获取了太多数据,并且一些集合试图将它们全部存储在一个连续的数组中 - 导致内存不足异常,因为没有足够大的可用内存插槽。然后我意识到,如果是这样的话,我们的代码——从数据库中加载数据——应该出现在调用堆栈中。它没有。
  • 列表重新分配,字符串拆分也会导致OOM,因为它们会造成内存碎片
  • @JoakimM.H。不要那么肯定。将 1M 项一一添加到 List 将导致内部缓冲区重新分配 20 次。第 1045505 项将产生一个新的 2097152 长缓冲区。在一个紧密的循环中执行此操作,GC 将没有时间清理它们。所有这些重新分配都会使内存碎片化,以至于没有足够大的内存块用于新的大缓冲区
  • 地址空间的碎片化在 32 位是可能的。在 64 位应用程序中,这不太可能成为问题。
  • @GarrGodfrey 是的 - 没有内部异常。客户看到这一点是因为我们有 Application.ThreadException 和 AppDomain.CurrentDomain.UnhandledException 的处理程序,它们将错误发送到我们的错误记录系统,并向用户显示“出了点问题”-弹出窗口比 Windows 更友好“应用程序已停止工作。”。
猜你喜欢
  • 2011-11-01
  • 1970-01-01
  • 2023-03-15
  • 1970-01-01
  • 1970-01-01
  • 2013-07-30
  • 1970-01-01
  • 1970-01-01
  • 2017-01-21
相关资源
最近更新 更多