19

I had some problems with a WCF web service (some dumps, memory leaks, etc.) and I run a profillng tool (ANTS Memory Profiles).

Just to find out that even with the processing over (I run a specific test and then stopped), Generation 2 is 25% of the memory for the web service. I tracked down this memory to find that I had a dictionary object full of (null, null) items, with -1 hash code.

The workflow of the web service implies that during specific processing items are added and then removed from the dictionary (just simple Add and Remove). Not a big deal. But it seems that after all items are removed, the dictionary is full of (null, null) KeyValuePairs. Thousands of them in fact, such that they occupy a big part of memory and eventually an overflow occurs, with the corresponding forced application pool recycle and DW20.exe getting all the CPU cycles it can get.

The dictionary is in fact Dictionary<SomeKeyType, IEnumerable<KeyValuePair<SomeOtherKeyType, SomeCustomType>>> (System.OutOfMemoryException because of Large Dictionary) so I already checked if there is some kind of reference holding things.

The dictionary is contained in a static object (to make it accesible to different processing threads through processing) so from this question and many more (Do static members ever get garbage collected?) I understand why that dictionary is in Generation 2. But this is also the cause of those (null, null)? Even if I remove items from dictionary something will be always occupied in the memory?

It's not a speed issue like in this question Deallocate memory from large data structures in C# . It seems that memory is never reclaimed.

Is there something I can do to actually remove items from dictionary, not just keep filling it with (null, null) pairs? Is there anything else I need to check out?

Community
  • 1
  • 1
Coral Doe
  • 1,925
  • 3
  • 19
  • 36
  • 1
    +!: For doing proper research and writing a good question. – Daniel A.A. Pelsmaeker Feb 22 '13 at 11:03
  • 1
    `List<>` has a `TrimExcess()` method, `Dictionary<,>` unfortunately does not. Thought this was an easy score :) – C.Evenhuis Feb 22 '13 at 11:22
  • Does calling Dictionary.Clear() make any difference? – Alex Feb 22 '13 at 12:18
  • @C.Evenhuis: I'm gonna trade the `Dictionary` performances for the `List` `TrimExcess()`, but it would have been a good idea. Althought others seemed interested in this: http://stackoverflow.com/questions/1625122/why-is-there-no-dictionary-trimexcess – Coral Doe Feb 22 '13 at 15:29
  • @Alex: I have tried calling `Clear()` and checked the operations really occurs, but it does not change with anything the described behaviour. – Coral Doe Feb 22 '13 at 15:44

3 Answers3

17

Dictionaries store items in a hash table. An array is used internally for this. Because of the way hash tables work, this array must always be larger than the actual number of items stored (at least about 30% larger). Microsoft uses a load factor of 72%, i.e. at least 28% of the array will be empty (see An Extensive Examination of Data Structures Using C# 2.0 and especially The System.Collections.Hashtable Class and The System.Collections.Generic.Dictionary Class) Therefore the null/null entries could just represent this free space.

If the array is too small, it will grow automatically; however, when items are removed, the array does not shrink, but the space that will be freed up should be reused when new items are inserted.

If you are in control of this dictionary, you could try to re-create it in order to shrink it:

theDict = new Dictionary<TKey, IEnumerable<KeyValuePair<TKey2, TVal>>>(theDict);

But the problem might arise from the actual (non empty) entries. Your dictionary is static and will therefore never be reclaimed automatically by the garbage collector, unless you assign it another dictionary or null (theDict = new ... or theDict = null). This is only true for the dictionary itself which is static, not for its entries. As long as references to removed entries exist somewhere else, they will persist. The GC will reclaim any object (earlier or later) which cannot be accessed any more through some reference. It makes no difference, whether this object was declared static or not. The objects themselves are not static, only their references.


As @RobertTausig kindly pointed out, since .NET Core 2.1 there is the new Dictionary.TrimExcess(), which is what you actually wanted, but didn't exist back then.

Olivier Jacot-Descombes
  • 104,806
  • 13
  • 138
  • 188
  • Thank you for the information, things get clearer for me. Although I see the advantages of available free memory, I would still like to `shrink` it in the manner you and @usr suggested. I just have to analyze better when I want it done. – Coral Doe Feb 22 '13 at 15:55
  • @Olivier By saying it does not shrink when items are removed, does that mean that the memory will still be used ? – sm_ Aug 22 '13 at 15:21
  • @SirajMansour, Removing items does not change the size of the hash table array. Value type keys and values are stored directly in this array. For reference types, only the reference (or `null`, after an item has been removed) is stored in the array. The objects memory to which this references pointed, can be reclaimed by the garbage collector, if they are not referenced somewhere else. – Olivier Jacot-Descombes Dec 19 '19 at 15:08
5

Looks like you need to recycle space in that dict periodically. You can do that by creating a new one: new Dictionary<a,b>(oldDict). Be sure to do this in a thread-safe manner.

When to do this? Either on the tick of a timer (60sec?) or when a specific number of writes has occurred (100k?) (you'd need to keep a modification counter).

usr
  • 168,620
  • 35
  • 240
  • 369
0

A solution could be to call Clear() method on the static dictionary. In this way, the reference to the dictionary will remain available, but the objects contained will be released.

bre_dev
  • 562
  • 3
  • 21
  • Someone mentioned this in the comments, and I replied at that time that using the `Clear` method was not leading to what I have wanted. – Coral Doe Aug 27 '13 at 11:27