我在我的计算机上尝试了一下,结果如下:
- 使用Data作为类,并且在DoWork结尾没有使用
_list = null
-> 内存增加
- 使用Data作为结构体,并且在DoWork结尾没有使用
_list = null
-> 内存增加
- 使用Data作为类,并且在DoWork结尾使用了
_list = null
-> 内存稳定在150MB
- 使用Data作为结构体,并且在DoWork结尾使用了
_list = null
-> 内存增加
在没有注释掉
_list = null
的情况下,这个结果并不令人意外。因为还是有对_list的引用。即使永远不再调用
DoWork
,GC也无法知道它。
在第三种情况下,垃圾收集器表现出我们期望它具有的行为。
对于第四种情况,当你将
Data
作为参数传递给
collection.Add(new Data(l));
时,BlockingCollection会存储它,但接下来会发生什么呢?
- 创建一个新的结构体
data
,其data._list
等于l
(即类型List
是类(引用类型),在结构体Data
中,data._list
等于l
的地址)。
- 然后将其作为参数传递给
collection.Add(new Data(l));
,然后创建data
的副本。然后复制了l
的地址。
- 阻塞集合将您的
Data
元素存储在数组中。
- 当
DoWork
执行_list = null
时,它仅从当前结构体中删除对有问题的List
的引用,而不是从存储在BlockingCollection
中的所有复制版本中删除。
- 然后,你就会遇到问题,除非你清除
BlockingCollection
。
如何找到问题?
为了查找内存泄漏问题,我建议您使用SOS(
http://msdn.microsoft.com/en-us/library/bb190764.aspx)。
这里,我介绍一下我是如何找到问题的。由于这是一个涉及堆栈的问题,使用堆分析(如此处)并不是找到问题源头的最佳方法。
1 在
_list = null
上设置断点(因为这行代码应该可以工作!)
2 执行程序
3 当达到断点时,加载SOS调试工具(在立即窗口中输入“ .load sos”)。
4 问题似乎来自于未正确释放的private List> _list
。因此,我们将尝试查找该类型的实例。在立即窗口中键入!DumpHeap -stat -type List
。结果:
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
问题类型是最后一个 List<Dictionary<...>>
。有两个实例,MethodTable(类型的一种引用)为00202800
。
5 要获取引用,请键入!DumpHeap -mt 00202800
。结果:
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
以下是两个实例及其地址:02618a9c
和0733880c
6 查找它们的引用方式:输入!GCRoot 02618a9c
(第一个实例)或!GCRoot 0733880c
(第二个实例)。结果如下(我没有复制所有结果,但保留了重要部分):
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]])
首先,针对第一个实例:
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
对于第二种情况(当分析对象没有更深的根时,我认为它意味着它在堆栈中有引用),我们应该查看026187bc(ConsoleApplication1.Data[])
以了解发生了什么,因为我们最终看到了我们的Data
类型。
7 要显示对象的内容,请使用!DumpObj 026187bc
,或者在这种情况下,作为数组,使用!DumpArray -details 026187bc
。结果(部分):
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
这里我们有数组的前三个元素的_list
属性的值: 02618a9c
, 6d50950800000000
, 6d50950800000000
。我怀疑6d50950800000000
是“空指针”。
这里有你问题的答案:有一个数组(由阻塞集合引用(见6.)),其中直接包含我们想要垃圾回收器完成的_list
的地址。
8为了确保在执行行_line = null
时它不会改变,执行该行。
注意
如我所提到的,使用DumpHeap对于涉及值类型的当前任务并不适合。为什么?因为值类型不在堆上而在栈上。看起来很简单:在断点上尝试!DumpHeap -stat -type ConsoleApplication1.Data
。结果:
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
有一个 Data
的数组但没有单独的 Data
。因为 DumpHeap 只分析堆。然后 !DumpArray -details 026187bc
,指针仍然与原来相同。如果在执行这行代码前后比较我们之前找到的两个实例的根(使用 !GCRoot
),只会有一行被移除。实际上,对于值类型 Data
的引用只被从一个副本中删除了。
Work
方法中的while(true)
有关? - Destrictor