I tried on my computer here is the result:
- With Data as a class and without
_list = null at the end of DoWork -> memory increases - With Data as a structure and without
_list = null at the end of DoWork -> memory increases - With Data as a class and with
_list = null at the end of DoWork -> memory is stabilized at a speed of 150 MB - With Data as a struct and with
_list = null at the end of DoWork -> memory increases
In cases where the comment _list = null commented out, it is not surprising to see this result. Because there is still a link to _list. Even if DoWork is never called again, the GC cannot know it.
In the third case, the garbage collector has the behavior that we expect from it.
In the fourth case, the BlockingCollection stores Data when you pass it as an argument to collection.Add(new Data(l)); but then what is done?
- A new
Data structure has been created with data._list equal to l (i.e. since the List type is a class (reference type), data._list is equal to struct Data at the address of l ). - Then you pass it as an argument to
collection.Add(new Data(l)); , then it creates a copy of Data created in 1. Then the address l copied. - The block block stores your
Data elements in an array. - When
DoWork does _list = null , it removes the link to the problematic List only in the current structure, and not in all copied versions that are stored in the BlockingCollection . - Then you have a problem if you do not clear the
BlockingCollection .
How to find a problem?
To find a memory leak problem, I suggest you use SOS ( http://msdn.microsoft.com/en-us/library/bb190764.aspx ).
Here I will tell you how I found the problem. Since this is a problem that involves not only a heap, but also a stack, using heap analysis (as here) is not the best way to find the source of the problem.
1 Put a breakpoint on _list = null (because this line should work !!!)
2 Run the program
3 . When the breakpoint is reached, load the SOS debugging tool (write ".load sos" in the Immediate window)
4 The problem seems to come from private List> _list , which is saved correctly. Therefore, we will try to find instances of the type. Type !DumpHeap -stat -type List in the Immediate window. Result:
total 0 objects Statistics: MT Count TotalSize Class Name 0570ffdc 1 24 System.Collections.Generic.List1[[System.Threading.CancellationTokenRegistration, mscorlib]] 04f63e50 1 24 System.Collections.Generic.List1[[System.Security.Policy.StrongName, mscorlib]] 00202800 2 48 System.Collections.Generic.List1[[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]] Total 4 objects
The problematic type is the last List<Dictionary<...>> . There are 2 instances, and MethodTable (type reference type) is 00202800 .
5 To get the links, enter !DumpHeap -mt 00202800 . Result:
Address MT Size 02618a9c 00202800 24 0733880c 00202800 24 total 0 objects Statistics: MT Count TotalSize Class Name 00202800 2 48 System.Collections.Generic.List1[[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]] Total 2 objects
Two copies are shown with their addresses: 02618a9c and 0733880c
6 To find how they refer: type !GCRoot 02618a9c (for the first instance) or !GCRoot 0733880c (for the second). Result (I did not copy the entire result, but retained an important role):
ESP:3bef9c:Root: 0261874c(ConsoleApplication1.Test1)-> 0261875c(System.Collections.Concurrent.BlockingCollection1[[ConsoleApplication1.Data, ConsoleApplication1]])-> 02618784(System.Collections.Concurrent.ConcurrentQueue1[[ConsoleApplication1.Data, ConsoleApplication1]])-> 02618798(System.Collections.Concurrent.ConcurrentQueue1+Segment[[ConsoleApplication1.Data, ConsoleApplication1]])-> 026187bc(ConsoleApplication1.Data[])-> 02618a9c(System.Collections.Generic.List1[[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]])
for the first instance and:
Scan Thread 5216 OSTHread 1460 ESP:3bf0b0:Root: 0733880c(System.Collections.Generic.List1[[System.Collections.Generic.Dictionary2[[System.String, mscorlib],[System.String, mscorlib]], mscorlib]]) Scan Thread 4960 OSTHread 1360 Scan Thread 6044 OSTHread 179c
for the second (when the analyzed object does not have a deeper root, I think this means that it has a link on the stack).
A look at 026187bc(ConsoleApplication1.Data[]) should be a good way to understand what will happen, because we finally see our Data type.
7 To display the contents of an object, use !DumpObj 026187bc or in this case, since it is an array, use !DumpArray -details 026187bc . Result (partial):
Name: ConsoleApplication1.Data[] MethodTable: 00214f30 EEClass: 00214ea8 Size: 140(0x8c) bytes Array: Rank 1, Number of elements 32, Type VALUETYPE Element Methodtable: 00214670 [0] 026187c4 Name: ConsoleApplication1.Data MethodTable: 00214670 EEClass: 00211ac4 Size: 12(0xc) bytes File: D:\Development Projects\Centive Solutions\SVN\trunk\CentiveSolutions.Renderers\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 00202800 4000001 0 ...lib]], mscorlib]] 0 instance 02618a9c _list [1] 026187c8 Name: ConsoleApplication1.Data MethodTable: 00214670 EEClass: 00211ac4 Size: 12(0xc) bytes File: D:\Development Projects\Centive Solutions\SVN\trunk\CentiveSolutions.Renderers\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 00202800 4000001 0 ...lib]], mscorlib]] 0 instance 6d50950800000000 _list [2] 026187cc Name: ConsoleApplication1.Data MethodTable: 00214670 EEClass: 00211ac4 Size: 12(0xc) bytes File: D:\Development Projects\Centive Solutions\SVN\trunk\CentiveSolutions.Renderers\ConsoleApplication1\bin\Debug\ConsoleApplication1.exe Fields: MT Field Offset Type VT Attr Value Name 00202800 4000001 0 ...lib]], mscorlib]] 0 instance 6d50950800000000 _list
Here we have the value of the _list attribute for the first three elements of the array: 02618a9c , 6d50950800000000 , 6d50950800000000 . I suspect 6d50950800000000 is a null pointer.
Here we have the answer to your question: there is an array (referred to by the lock collection (see 6.)) that directly contains the address _list , which we want the garbage collector to complete.
8 To ensure that it does not change when the _line = null line is executed, the line is executed.
Note
As I said before, using DumpHeap is not good for the current task, which implies value types. What for? Because value types are not on the heap, but on the stack. This is very easy to see: try it !DumpHeap -stat -type ConsoleApplication1.Data at the breakpoint. Result:
total 0 objects Statistics: MT Count TotalSize Class Name 00214c00 1 20 System.Collections.Concurrent.ConcurrentQueue`1[[ConsoleApplication1.Data, ConsoleApplication1]] 00214e24 1 36 System.Collections.Concurrent.ConcurrentQueue`1+Segment[[ConsoleApplication1.Data, ConsoleApplication1]] 00214920 1 40 System.Collections.Concurrent.BlockingCollection`1[[ConsoleApplication1.Data, ConsoleApplication1]] 00214f30 1 140 ConsoleApplication1.Data[] Total 4 objects
There is an array of Data , but not Data . Because DumpHeap parses only a bunch. Then !DumpArray -details 026187bc pointer is still here with the same value. And if you compare the roots of two instances that we discovered earlier (with !GCRoot ), only the line will be deleted before and after the line is executed. Indeed, list binding is only removed from 1 copy of the Data value type.