我在我的电脑上试过了,结果如下:
- 使用 Data 作为类并且
_list = null
在 DoWork 结束时没有 -> 内存增加
- 使用 Data 作为结构并且
_list = null
在 DoWork 结束时没有 -> 内存增加
- 使用 Data 作为类并
_list = null
在 DoWork 结束时使用 -> 内存稳定在 150MB
- 使用 Data 作为结构并
_list = null
在 DoWork 结束时使用 -> 内存增加
在被评论的情况下,_list = null
看到这个结果并不奇怪。因为还有对_list的引用。即使DoWork
不再调用,GC 也不知道。
在第三种情况下,垃圾收集器具有我们期望的行为。
对于第四种情况,Data
当您将 BlockingCollection 作为 in 的参数传递时,BlockingCollection 会存储它collection.Add(new Data(l));
,但是接下来会做什么呢?
- 一个新的结构
data
是用data._list
等于创建的l
(即类型List
是一个类(引用类型),data._list
在结构中等于Data
的地址l
)。
- 然后你将它作为参数传递,
collection.Add(new Data(l));
然后它会创建一个data
在 1 中创建的副本。然后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<...>>
。有 2 个实例,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
数组的前 3 个元素的属性值: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
)找到的两个实例的根,则只会删除行。实际上,对列表的引用仅从值类型的 1 个副本中删除Data
。