6

My (C# .NET 4.0) application runs over a period of days, updating a simulated account according to changes in prices acquired from a SQLite database.

All I need on any particular date is the account in its current state and the latest prices. I'd expect that the Garbage Collector would keep memory usage on a fairly even keel: what I'm seeing is a steady increase in Working Set and Private Memory (as reported by System.Diagnostics.GetCurrentProcess()) and also in GC.GetTotalMemory(true): about 300K per day in this case. Inevitably, the whole thing crashes after about 12 simulated years, at which point the memory use has increased by about 1GB.

Memory usage increases more-or-less linearly, (very much more smoothly if I force a GC.Collect() at the end of each day).

I'm inferring that some objects are somehow not garbage-collectible, even when I think there's no further need for them and had expected that they would be cleaned up in the normal ebb and flow of execution.

What might I try to identify where I've inadvertently managed to create such a situation?

I've downloaded and run CLRProfiler - it's going to take the best part of the weekend to digest the documentation, though, and there's no guarantee that it'll be able to help.

I'm working through the references in this question. In general, I know what kind of situation can be causing the problem, I'm more interested to see if there are faster ways to identify the specifics without spending precious days working through my code ticking off references...

NOTE: The problem doesn't appear to be event-related and there's no graphic component involved.

Community
  • 1
  • 1
Mike Woodhouse
  • 51,832
  • 12
  • 88
  • 127
  • Ironically, I found the leak(s) by watching the process in [ProcExp](http://technet.microsoft.com/en-us/sysinternals/bb896653), where I noticed my SQLite database started to appear in the file list hundreds of times. Not sure how I "achieved" this - some failure to close the file after use, although I can't see where. I was able to rework the code to stop the constant file-opening which will let me move forward while I consider the file thing on a separate thread. – Mike Woodhouse Dec 06 '11 at 12:39
  • 1
    Hah! Turns out there was a file handle leak in System.Data.SQLite, which has been fixed in the latest version (1.0.77.0). Hurrah. – Mike Woodhouse Dec 06 '11 at 15:25

3 Answers3

7

You said you can simulate workload to cause your application to crash. To quickly find your leak, simulate workload until memory consumption becomes significant (e.g. 100 MB), attach WinDbg or VS with unmanaged debugging to your process and run in immediate window:

.load sos
extension C:\WINDOWS\Microsoft.NET\Framework\v2.0.50727\sos.dll loaded
!dumpheap -stat
total 2885 objects
Statistics:
MT    Count    TotalSize Class Name
7a5eab18        1           12 System.Diagnostics.TraceOptions
(lots of unimportant stuff here)
79332b54      304         7296 System.Collections.ArrayList
79333274       56        11640 System.Collections.Hashtable+bucket[]
793042f4      345        50056 System.Object[]
79330b24     1041        99428 System.String
79332cc0       21    107109728 System.Int32[]

From this (sorted) output you can tell you are having a problem with int[] type. Type:

!DumpHeap -type System.Int32[] (or whichever type you got)
Address       MT     Size
01381a1c 7931e9bc       40     
(cut)
075d1000 79332cc0 67108880     
03811000 79332cc0 40000016     
total 22 objects
Statistics:
MT    Count    TotalSize Class Name
7931e9bc        1           40 System.Int32[][]
79332cc0       21    107109728 System.Int32[]
Total 22 objects

Now you have address of your object(s). Pick one of them and run

!GCRoot 075d1000

And you'll get cause of your leak. Also, you might want to consult this tutorial.

MagnatLU
  • 5,967
  • 1
  • 22
  • 17
  • 1
    I really like this answer, but I wanted to add simply that if something is not being GC'd it means a reference to it is being stored and not removed. You have somewhere, something still tracking it. The eventual solution will be to find this trouble code. – Dracorat Dec 02 '11 at 16:37
4

Are you using events? If object A is subscribed to an event on object B, then B is storing a reference to A. Therefore A can't be garbage collected until B can be garbage collected. If A unsubscribes from B, then B's reference to A is removed.

Greg
  • 23,155
  • 11
  • 57
  • 79
  • I saw this in some WPF apps I was troubleshooting when users were subscribing to events via the view models. Good suggestion!!!! – tsells Dec 02 '11 at 19:39
2

One thing that I'd suggest looking for as a heuristic is places where you might have attached to an event in a longer lived instance from a shorter lived one. If you have some instance that sticks around for the lifetime of your application, and you have shorter lived classes that listen for events on it, failing to unhook those events will result in your shorter-lived objects being skipped by the garbage collector, since the longer lived object hangs onto a reference to them until you detach.

Edit: for what it's worth, you might want to check out Ants Memory Profiler. I don't know how it compares to the one you're looking at it, but from having tried it out, the learning curve on it isn't too bad, so you might spend less time figuring it out.

Erik Dietrich
  • 6,080
  • 6
  • 26
  • 37