WinDbg with dump files when you have a classic ASP app which uses a lot of .Net via interop

0

Ok, first please don't ask why this application is the way it is. This is a classic ASP application which in several areas uses .Net and COM and some of that .Net also reaches into COM! It's all to do with code reuse really, at some point .Net was introduced and used for a whole load of features, which were then required within the classic ASP app and voile! some kind of hellish beast appeared.

So my question? I can see what looks like a serious memory leak in the application (it's 32 bit) where private bytes will suddenly rocket up to 900mb+ and users will start to receive out of memory errors and some odd issues like part of the page being rendered. Initially I simply took some perfmon logs and added some user action tracing code and tied the data from the trace to the perfmon (working set and private bytes on w3wp). This told me a few things, which I did find issues with, "resolved" and release - though they had NO impact. A stress test on the app based on the actions in the IIS logs, also told me nothing - I can't replicate this at all locally.

So I got a memory dump when it hits around 900mb and am analysing with WinDbg. The problem is - I have hardly used this tool in the past, and it was a long time ago when I did. I ran a few things like:

.loadby sos mscorwks
!dumpheap -stat

I get System.String as the biggest user of memory and I dumped out all the strings to the console, but it's fairly meaningless, just a load of strings I might expect to see. I can't see the wood for the trees it seems.

Does anyone have any advice on how I can interpret a dmp file and determine what is REALLY using most of the memory? The fact that System.String "appears" to be using most of it (it's at the bottom), isn't quite true I believe. This is also not strictly a .Net app, so how can I get an idea of whether .Net or classic ASP/COM is using up most of the memory?

UPDATE 1

Thanks for the answers so far from Thomas and Alex. I'm struggling to find the root cause, but have gone a little deeper to the point where I see a common pattern/theme emerging. Please see the following:

I look for a summary of memory usage:

>!heap -s
 Heap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast 
                    (k)     (k)    (k)     (k) length      blocks cont. heap 
-----------------------------------------------------------------------------
002a0000 58001062  695360 677844 680772   2102   100   420    8  1f960   L  

I redacted the results to show one, but the clear majority of memory in the head is used by this address. Then I look at that address and can see most of the objects are of size 2404, so I look to see what these are:

>!heap -stat -h 002a0000
size     #blocks     total     ( %) (percent of total busy bytes)
2404 cfce - 1d3c3738  (71.05)
>!heap -flt s 2404

A VERY large list is returned as expected and by ensuring +ust gflags are on for w3wp before collecting the dump and investigating a number of these i get a fairly descriptive stack trace:

>!heap -p -a 4fc4d7b8
 HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
    4fc4d7b0 0484 0000  [07]   4fc4d7b8    02404 - (busy)
    Trace: 414f
    7c833ba5 ntdll!RtlInitializeCriticalSection+0x00000010
    77e67877 kernel32!InitializeCriticalSection+0x0000000e
    79e8539b mscorwks!CrstBase::InitWorker+0x000000a7
    79e853d4 mscorwks!Crst::Crst+0x00000016
    79e92b58 mscorwks!PendingTypeLoadEntry::PendingTypeLoadEntry+0x0000001c
    79e92992 mscorwks!ClassLoader::LoadTypeHandleForTypeKey_Inner+0x000000bd
    79e92b01 mscorwks!ClassLoader::LoadTypeHandleForTypeKey_Body+0x000001da
    79e9272a mscorwks!ClassLoader::LoadTypeHandleForTypeKey+0x000000ae
    79e9359f mscorwks!ClassLoader::EnsureLoaded+0x000000a6
    79e932d9 mscorwks!MethodTable::DoFullyLoad+0x000000c1
    79e9302f mscorwks!MethodTable::DoFullyLoad+0x00000180
    79e9333c mscorwks!MethodTable::DoFullyLoad+0x00000142
    79e92dc9 mscorwks!ClassLoader::Notify+0x00000104
    79e92764 mscorwks!ClassLoader::LoadTypeHandleForTypeKey+0x000000e8
    79e934b3 mscorwks!ClassLoader::LoadTypeDefThrowing+0x00000193
    79e94515 mscorwks!ClassLoader::LoadTypeHandleThrowing+0x000001f4
    79e8c1f0 mscorwks!ClassLoader::LoadTypeHandleThrowIfFailed+0x0000001b
    79efd389 mscorwks!ClassLoader::LoadTypeByNameThrowing+0x0000003b
    79efd315 mscorwks!Binder::LookupClass+0x00000032
    79efd3ab mscorwks!Binder::FetchClass+0x0000001f
    79f1ec2b mscorwks!RefSecContext::Init+0x000000ba
    79ef3726 mscorwks!RuntimeTypeHandle::CreateInstance+0x00000334
    7d0663a +0x07d0663a
    7d06567 +0x07d06567
    7d063b5 +0x07d063b5
    7d05ec3 +0x07d05ec3
    7d05e60 +0x07d05e60
    7d05e22 +0x07d05e22
    7d05dda +0x07d05dda
    7d05cbe +0x07d05cbe
    7d05c44 +0x07d05c44
    7d0582d +0x07d0582d

or

> !heap -p -a 4fca7ff0
address 4fca7ff0 found in
_HEAP @ 2a0000
  HEAP_ENTRY Size Prev Flags    UserPtr UserSize - state
    4fca7fe8 0484 0000  [07]   4fca7ff0    02404 - (busy)
    Trace: 414b
    7c854f44 ntdll!RtlAllocateHeapSlowly+0x00000041
    7c83d7f0 ntdll!RtlAllocateHeap+0x00000e9f
    79e747ac mscorwks!EEHeapAlloc+0x00000142
    79e7482a mscorwks!EEHeapAllocInProcessHeap+0x00000052
    79e74853 mscorwks!operator new[]+0x00000025
    79e927d9 mscorwks!PendingTypeLoadTable::AllocNewEntry+0x0000000c
    79e929ae mscorwks!ClassLoader::LoadTypeHandleForTypeKey_Inner+0x000000d9
    79e92b01 mscorwks!ClassLoader::LoadTypeHandleForTypeKey_Body+0x000001da
    79e9272a mscorwks!ClassLoader::LoadTypeHandleForTypeKey+0x000000ae
    79e9359f mscorwks!ClassLoader::EnsureLoaded+0x000000a6
    79e92faa mscorwks!ClassLoader::LoadExactParentAndInterfacesTransitively+0x000000c2
    79e92fe7 mscorwks!ClassLoader::LoadExactParents+0x00000027
    79e92f24 mscorwks!ClassLoader::DoIncrementalLoad+0x00000057
    79e929e1 mscorwks!ClassLoader::LoadTypeHandleForTypeKey_Inner+0x0000012c
    79e92b01 mscorwks!ClassLoader::LoadTypeHandleForTypeKey_Body+0x000001da
    79e9272a mscorwks!ClassLoader::LoadTypeHandleForTypeKey+0x000000ae
    79e934b3 mscorwks!ClassLoader::LoadTypeDefThrowing+0x00000193
    79e94515 mscorwks!ClassLoader::LoadTypeHandleThrowing+0x000001f4
    79e8c1f0 mscorwks!ClassLoader::LoadTypeHandleThrowIfFailed+0x0000001b
    79efd389 mscorwks!ClassLoader::LoadTypeByNameThrowing+0x0000003b
    79efd315 mscorwks!Binder::LookupClass+0x00000032
    79efd3ab mscorwks!Binder::FetchClass+0x0000001f
    79f1ec2b mscorwks!RefSecContext::Init+0x000000ba
    79ef3726 mscorwks!RuntimeTypeHandle::CreateInstance+0x00000334
    7d0663a +0x07d0663a
    7d06567 +0x07d06567
    7d063b5 +0x07d063b5
    7d05ec3 +0x07d05ec3
    7d05e60 +0x07d05e60
    7d05e22 +0x07d05e22
    7d05dda +0x07d05dda
    7d05cbe +0x07d05cbe

What I would love to be able to do at this point is see exactly what classes are being instantiated to give me a clue which user action (always from a classic ASP page) is calling what (e.g. cASP calls COM or .Net which in turn could call COM or .Net).

.net
memory-leaks
asp-classic
windbg
asked on Stack Overflow Jun 18, 2014 by Mr AH • edited Jun 23, 2014 by Mr AH

2 Answers

0

You need to find the roots that holds these strings in memory. I have a few examples in my article: http://alexatnet.com/articles/net-memory-management-and-garbage-collector but generally what you might need to do is to use !gcroot command - it should traverse object graph to one of the roots that holds this object.

answered on Stack Overflow Jun 19, 2014 by Alex Netkachov
0

There are two different heap types: native heaps (heaps of the heap manager) and managed heaps (heaps created by the .NET runtime). What you see as the output of !dumpheap is only the managed part. Since your COM objects are also using native memory, this is not included in the output.

To see the native part of the memory, try !address -summary. .NET memory will show up as <unknown> and native memory will be listed as Heap in the usage summary.

Still, !dumpheap can be helpful, e.g. to see the number of RCW objects created by your application. RCWs are not very large, therefore they might not be listed near the end of the output. Try !dumpheap -stat -type Interop to find them (if you`re using the default interop assembly).

If you know how large your COM objects are on native side, you can just multiply the number of object by the memory usage. In my typical environment, I'm using different COM objects with 5 MB to 100 MB in size, so even a few ones can cause OutOfMemoryException.

Knowing the exact size of a COM object is good for the use of GC.AddMemoryPressure which you can then use.

answered on Stack Overflow Jun 19, 2014 by Thomas Weller

User contributions licensed under CC BY-SA 3.0