【问题标题】:High memory usage for a .Net application with lots of handles具有大量句柄的 .Net 应用程序的高内存使用率
【发布时间】:2016-03-15 09:50:48
【问题描述】:

我正在使用带有进程转储的 WinDbg 对内存压力高的 .net 应用程序进行事后分析,该进程是 Windows 服务。

我感觉这 14GB 进程内存消耗中的大部分来自中止的线程,因此有很多孤立信号量/事件/突变等。但我无法将所有这些放在一起并将它们加起来就像多少单个信号量/事件/突变所需的内存以及哪种 WinDbg 命令对这种情况有帮助?

以下是 WinDbg 输出:

!handle

**Type                      Count**

None                        90
Event                       5550
Section                     41
File                        1166
Directory                   3
Mutant                      160
Semaphore                   4581
Key                         78
Token                       2
Thread                      553
IoCompletion                6
Timer                       1
TpWorkerFactory             3
ALPC Port                   9
WaitCompletionPacket        33

!address -summary

--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal

<unknown>                               471        3`86ea2000 (  14.108 Gb)  92.40%    0.01%

!threads(列出的许多线程都有 ThreadAbortException 异常)

Lock  
   ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception

  12    3 33f0 00000017e3c23200 1282b221 Preemptive  0000000000000000:0000000000000000 00000017e3bb3930 1     MTA System.Threading.ThreadAbortException 000000181de5d668

【问题讨论】:

  • 由于许多未完成的操作系统资源,难怪您会看到高内存使用率。一个常见的原因是死锁的终结器线程,一旦发生这种情况,内存使用量就会无限攀升。在 minidump 中找到它并查看它的调用堆栈。
  • @HansPassant 似乎终结器线程确实被以下输出阻止了,但我在代码中没有看到终结器存在的任何地方:0:000> ~[2]k Child-SP RetAddr呼叫站点 00000017fd5bf2c8 00007ffcd340dd29 ntdll!NtWaitForSingleObject+0xa 00000017fd5bf2d0 00007ffcd340b6f4 ntdll!RtlpWaitOnCriticalSection+0xe1
  • 它几乎总是被阻塞,重要的是它是否死锁。您发布的堆栈跟踪信息不足以让我们看到。考虑致电 Microsoft 支持以获取相关帮助。

标签: .net windows-services windbg postmortem-debugging


【解决方案1】:

句柄及其包装器可能非常小。使用dt -r 了解它们的大小:

0:025> dt -r ntdll!_KSEMAPHORE
   +0x000 Header           : _DISPATCHER_HEADER
      +0x000 Type             : UChar
      +0x001 TimerControlFlags : UChar
      +0x001 Absolute         : Pos 0, 1 Bit
      +0x001 Coalescable      : Pos 1, 1 Bit
      +0x001 KeepShifting     : Pos 2, 1 Bit
      +0x001 EncodedTolerableDelay : Pos 3, 5 Bits
      +0x001 Abandoned        : UChar
      +0x001 Signalling       : UChar
      +0x002 ThreadControlFlags : UChar
      +0x002 CpuThrottled     : Pos 0, 1 Bit
      +0x002 CycleProfiling   : Pos 1, 1 Bit
      +0x002 CounterProfiling : Pos 2, 1 Bit
      +0x002 Reserved         : Pos 3, 5 Bits
      +0x002 Hand             : UChar
      +0x002 Size             : UChar
      +0x003 TimerMiscFlags   : UChar
      +0x003 Index            : Pos 0, 6 Bits
      +0x003 Inserted         : Pos 6, 1 Bit
      +0x003 Expired          : Pos 7, 1 Bit
      +0x003 DebugActive      : UChar
      +0x003 ActiveDR7        : Pos 0, 1 Bit
      +0x003 Instrumented     : Pos 1, 1 Bit
      +0x003 Reserved2        : Pos 2, 4 Bits
      +0x003 UmsScheduled     : Pos 6, 1 Bit
      +0x003 UmsPrimary       : Pos 7, 1 Bit
      +0x003 DpcActive        : UChar
      +0x000 Lock             : Int4B
      +0x004 SignalState      : Int4B
      +0x008 WaitListHead     : _LIST_ENTRY
         +0x000 Flink            : Ptr64 _LIST_ENTRY
         +0x008 Blink            : Ptr64 _LIST_ENTRY
   +0x018 Limit            : Int4B

所以信号量的大小是 0x18+4 字节。请注意,如果使用 Ptr 部分,大小可能会增加,例如如果您的候补名单很大。

我建议在您的情况下使用!dumpheap -stat。这将输出一个 .NET 对象列表,按它们的总大小排序。

通常有 byte[]object[](或类似的)和 String 占用最多的内存。

0:025> !dumpheap -mt 000007feef0aea80
         Address               MT     Size
[...]
000007feef0bda88     4915       329862 System.String
000007feef0be100     1419       382288 System.Object[]
000007feef05f748        2       786520 System.UInt32[]
Total 65575 objects

对于单个对象,您可以使用!objsize &lt;address&gt;。在我的示例中,System.Threading.Thread 有 16 kB。因此,即使您有 1000 个线程,这也只能弥补 16 MB 的内存。 System.Threading.ManualResetEvent 的示例在我的应用程序中仅占 80 个字节。

0:025> !objsize 0000000011878358 
sizeof(0000000011878358) = 16736 (0x4160) bytes (System.Threading.Thread)

0:025> !objsize 00000000118af810 
sizeof(00000000118af810) = 80 (0x50) bytes (System.Threading.ManualResetEvent)

【讨论】:

  • 不知道你得罪了谁,但似乎有人在疯狂投票
  • @LievenKeersmaekers:谢谢。我不再关心声誉,只要我保持在 2000 以上就能进行编辑而不会造成大量审查工作。
猜你喜欢
  • 2013-08-18
  • 2015-04-02
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-07-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多