铁链总是很难剪断的,.NET的对象引用链条就犹如铁链那么坚固。稍有不注意,你就会因为这个链条而让你的程序背上沉重的负担,进而让你的程序Crash。接下来我们就结合WinDbg来分析一个这样的例子。
上回说到,解决了Lucene.net的缓存问题后,内存依久就是如洪水水位一样,无情的暴涨,马上又到了警戒水位了,没有办法,只有炸堤防洪。要得彻底的解决问题,还得依靠Windbg和dump文件。这回,我捕捉到一个也将近1G左右的dump文件。好吧,让我们重新开始吧。查看拖管堆:
0:011> !eeheap -gc
PDB symbol for mscorwks.dll not loaded
Number of GC Heaps: 4
------------------------------
Heap 0 (000e22d0)
generation 0 starts at 0x473a8500
generation 1 starts at 0x4738941c
generation 2 starts at 0x02860038
ephemeral segment allocation context: none
segment begin allocated size
1b84dc60 7b45382c 7b46a268 0x00016a3c(92732)
000f66d8 7a72c42c 7a74d308 0x00020edc(134876)
000eaef0 790d5588 790f4b38 0x0001f5b0(128432)
02860000 02860038 0685cc24 0x03ffcbec(67095532)
30000000 30000038 33ffe93c 0x03ffe904(67102980)
44350000 44350038 475e4afc 0x03294ac4(53037764)
Large object heap starts at 0x12860038
segment begin allocated size
12860000 12860038 144620a8 0x01c02070(29368432)
1fc00000 1fc00038 1fe659b0 0x00265978(2513272)
50fc0000 50fc0038 5178d060 0x007cd028(8179752)
Heap Size 0xd91b88c(227653772)
------------------------------
Heap 1 (000e3538)
generation 0 starts at 0x6e30f94c
generation 1 starts at 0x6e2dad60
generation 2 starts at 0x06860038
ephemeral segment allocation context: none
segment begin allocated size
06860000 06860038 0a85ff64 0x03ffff2c(67108652)
22000000 22000038 25ffffe0 0x03ffffa8(67108776)
38350000 38350038 3c33b76c 0x03feb734(67024692)
6d110000 6d110038 6e4fa678 0x013ea640(20883008)
Large object heap starts at 0x14860038
segment begin allocated size
14860000 14860038 1489a178 0x0003a140(237888)
Heap Size 0xd40fd88(222363016)
------------------------------
Heap 2 (000e4a48)
generation 0 starts at 0x441e9d28
generation 1 starts at 0x441d3978
generation 2 starts at 0x0a860038
ephemeral segment allocation context: none
segment begin allocated size
0a860000 0a860038 0e85fb00 0x03fffac8(67107528)
2a000000 2a000038 2dffd6a4 0x03ffd66c(67098220)
40350000 40350038 441f5d34 0x03ea5cfc(65690876)
Large object heap starts at 0x16860038
segment begin allocated size
16860000 16860038 16860048 0x00000010(16)
Heap Size 0xbea2e40(199896640)
------------------------------
Heap 3 (000e5f30)
generation 0 starts at 0x40224450
generation 1 starts at 0x401e7bcc
generation 2 starts at 0x0e860038
ephemeral segment allocation context: none
segment begin allocated size
0e860000 0e860038 1285f4b8 0x03fff480(67105920)
26000000 26000038 29ffeee0 0x03ffeea8(67104424)
3c350000 3c350038 4022445c 0x03ed4424(65881124)
Large object heap starts at 0x18860038
segment begin allocated size
18860000 18860038 18b13eb0 0x002b3e78(2834040)
Heap Size 0xc1865c4(202925508)
------------------------------
GC Heap Size 0x32d54a18(852838936)
从拖管堆的大小(800多M)可以看出,大部分的内存还是被拖管堆给占用了。而且我们还可以看到以前一个情节不同的是,这回,大对象并不是很多。我们可以从下面的命令得到验证:
0:011> !dumpheap -min 85000
------------------------------
Heap 0
Address MT Size
473ac50c 000e1888 1400656 Free
4750491c 000e1888 916416 Free
12881370 000e1888 1019752 Free
1297a718 7912254c 118160
129974a8 000e1888 411144 Free
129fbab0 79122610 121248
12a19460 79122610 4672296
12e8df88 79122610 4672296
13303ce0 000e1888 453440 Free
13373820 000e1888 449360 Free
133e29d0 000e1888 2834040 Free
13696a58 000e1888 3287480 Free
139b9620 000e1888 4480064 Free
13dff470 000e1888 3294072 Free
141239f8 000e1888 3400864 Free
1fc00038 000e1888 513424 Free
1fc7d9f8 000e1888 906888 Free
1fd5b080 7912254c 124488
1fd796c8 000e1888 966872 Free
50fc0038 000e1888 8055264 Free
5176ea18 7912254c 124488
total 21 objects
------------------------------
Heap 1
Address MT Size
6e317958 000e1888 1976064 Free
14860048 000e1888 113384 Free
1487bb30 7912254c 124488
total 3 objects
------------------------------
Heap 2
Address MT Size
total 0 objects
------------------------------
Heap 3
Address MT Size
18860048 000e1888 2720656 Free
18af83d8 7912273c 113368
total 2 objects
------------------------------
total 26 objects
Statistics:
MT Count TotalSize Class Name
7912273c 1 113368 System.Byte[]
7912254c 4 491624 System.Object[]
79122610 3 9465840 System.Collections.Hashtable+bucket[]
000e1888 18 37199840 Free
Total 26 objects
大对象(大于85000 bytes)的数量并不多,共有26个对象,而且还有18是已释放的。看来,从大对象找不到我们想要的结果。那我们就查看小一个等级的对象,看一下小于8500的对象都有哪些?
0:011> !dumpheap -min 8500
------------------------------
Heap 0
Address MT Size
790da154 790f9244 9280
0287c318 790f9244 16404
028804d4 790f9244 16404
02884f18 790f9244 32788
02aec324 7912273c 9604
02b1dffc 790f9244 16404
02b22be8 790f9244 32788
03038134 79122610 13248
0323006c 7912273c 11616
0586a068 7912254c 17904
3090bd60 7912273c 14836
30adaffc 7912273c 54188
31fc6d04 7912273c 25728
32954478 79122610 28008
33f4296c 790f9244 16404
33f47558 790f9244 32788
44581a38 790f9244 16404
44586624 790f9244 32788
4461668c 79122610 28008
44dad90c 790f9244 16404
44db1ac8 790f9244 16404
44db650c 790f9244 32788
44dd90f8 790f9244 16404
44dddce4 790f9244 32788
44df4004 790f9244 16404
44df81c0 790f9244 16404
......
3f028880 790f9244 16404
3f02d46c 790f9244 32788
3f182a1c 790f9244 16404
3f187608 790f9244 32788
3f1fd1dc 790f9244 16404
3f201dc8 790f9244 32788
3f25cef8 790f9244 16404
3f261ae4 790f9244 32788
3f2f36c8 790f9244 16404
3f2f82b4 790f9244 32788
3f7a87a8 7912254c 28204
4001eca4 000e1888 8760 Free
401d399c 790f9244 21988
18860048 000e1888 2720656 Free
18af83d8 7912273c 113368
total 111 objects
------------------------------
total 400 objects
Statistics:
MT Count TotalSize Class Name
79122868 1 22676 System.Int64[]
02803518 1 30908 System.Collections.Generic.Dictionary`2+Entry[[System.String, mscorlib],[System.Web.Configuration.AuthorizationSection, System.Web]][]
7912273c 17 484412 System.Byte[]
7912254c 14 707912 System.Object[]
790f9244 330 8062572 System.String
79122610 14 9715704 System.Collections.Hashtable+bucket[]
000e1888 23 37249276 Free
Total 400 objects
好的,共找到400个对象,这时我们看到一个好现象,那就是对象的大小都是有规律的,有规律就好办事。我还想看看这些都是些什么对象?
0:011> !dumpheap -stat
------------------------------
Heap 0
total 3617074 objects
------------------------------
Heap 1
total 4237968 objects
------------------------------
Heap 2
total 3868413 objects
------------------------------
Heap 3
total 3860949 objects
------------------------------
total 15584404 objects
Statistics:
MT Count TotalSize Class Name
7ae7d438 1 12 System.Drawing.ColorConverter
......看数量多的
1cfb3180 33572 1074304 System.EventHandler`1[[NBear.Web.Data.NBearDataSourceEventArgs, NBear.Web.Data]]
66408158 16788 1074432 System.Web.UI.HtmlControls.HtmlLink
1c21a8b4 10418 1083472 Shop.Code.Controls.InitQSCheckBox
663a2e58 5599 1097404 System.Web.HttpResponse
1cb989d4 11192 1119200 SoF.Web.Controls.AjaxExtender.NameValueAutoComplete
1cfb2504 5596 1141584 ASP.default_master
1c21bff4 15627 1187652 Shop.Code.Controls.InitQSTextBox
648f09f4 25240 1211520 System.Configuration.ConfigurationValues
663e20a4 21658 1386112 System.Web.UI.HtmlControls.HtmlMeta
1c6c2074 3362 1425488 ASP.product_sortproductlist_aspx
663a6f84 5599 1433344 System.Web.Hosting.ISAPIWorkerRequestInProcForIIS6
6638e588 19015 1445140 System.Web.Configuration.HttpHandlerAction
648f1150 40166 1445976 System.Configuration.ConfigurationLockCollection
7a74db74 72771 1455420 System.ComponentModel.EventHandlerList+ListEntry
663b8af4 47082 1506624 System.Web.UI.RenderMethod
663c8014 127279 1527348 System.Web.UI.CompiledTemplateBuilder
1cb9b2c4 5595 1566600 SoF.Web.Controls.DataControlPagerExtender
791293b4 68360 1640640 System.Collections.Generic.List`1[[System.String, mscorlib]]
7a74da98 109651 1754416 System.ComponentModel.EventHandlerList
663b9534 47083 2071652 System.Web.UI.Control+ControlRareFields
663b835c 32902 2105728 System.Web.UI.HtmlControls.HtmlGenericControl
648f0e74 106655 2133100 System.Configuration.ConfigurationValue
1c6cc8ec 68160 2181120 NBear.Common.Entity+QueryProxyHandler
1c6cc7cc 68160 2181120 NBear.Common.Entity+PropertyChangedHandler
6641160c 78330 2193240 System.Web.UI.WebControls.ListItem
663b631c 39171 2193576 System.Web.UI.WebControls.ContentPlaceHolder
7912273c 10349 2333704 System.Byte[]
663daa3c 127788 2555760 System.Web.UI.ScriptKey
1d4295f4 80531 2576992 Lucene.Net.Documents.Field
663b6f88 162409 2598544 System.Web.UI.AttributeCollection
7910c0f4 83555 2673760 System.EventHandler
6639b014 139791 2795820 System.Web.VirtualPath
663bd1f8 78022 2808792 System.Web.UI.DataSourceSelectArguments
1d38c240 179183 2881432 KTDictSeg.T_INNER_POS[]
1c1f2f44 27975 2909400 *.DataSources.HelpDataSources
653ba904 68162 3544424 System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object, mscorlib]]
1cf424dc 30282 3633840 System.Web.UI.WebControls.SoFRepeater
790fdb60 336963 4043556 System.Int32
6640f3ec 127279 4072928 System.Web.UI.BuildTemplateMethod
79122414 40873 4332792 System.Int32[]
663ca710 42532 4423328 System.Web.UI.WebControls.Repeater
66410058 327377 5238032 System.Web.UI.StateBag
790fd8b4 94095 5269320 System.Collections.Hashtable
663dabb0 269441 6466584 System.Web.HttpServerVarsCollectionEntry
02805218 136320 7088640 System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Collections.Generic.List`1[[System.Object, mscorlib]], mscorlib]]
7910234c 312379 7497096 System.Collections.ArrayList
7a7603a4 475247 7603952 System.Collections.Specialized.NameObjectCollectionBase+NameObjectEntry
1d38c17c 308770 8645560 FTAlgorithm.T_DfaUnit
7a752890 470437 9408740 System.Collections.Specialized.HybridDictionary
663c4b3c 629654 10074464 System.Web.UI.StateItem
7a752968 360636 10097808 System.Collections.Specialized.ListDictionary
1bd4e58c 63806 13016424 *.Entities.SimpleProduct
66412704 304939 18296340 System.Web.UI.LiteralControl
7a752a34 916368 18327360 System.Collections.Specialized.ListDictionary+DictionaryNode
663b70f0 515385 18553860 System.Web.UI.ControlCollection
663c00b8 285831 19436508 System.Web.UI.DataBoundLiteralControl
663ed42c 307365 20900820 System.Web.UI.WebControls.RepeaterItem
79122610 94101 32575176 System.Collections.Hashtable+bucket[]
000e1888 130 37305432 Free
6641cf70 922079 40571476 System.Web.UI.Control+OccasionalFields
7912254c 1433851 69899636 System.Object[]
790f9244 3565568 348747316 System.String
Total 15584404 objects
Fragmented blocks larger than 0.5 MB:
Addr Size Followed by
473ac50c 1.3MB 4750245c Lucene.Net.Search.TermScorer
4750491c 0.9MB 475e44dc System.Byte[]
6e317958 1.9MB 6e4fa058 System.Byte[]
奇了怪了?怎么都是System.Web.UI空间的对象?难道页面被请求后没有被及时释放?造成有大量的页面实例被存放在拖管堆当中。从!dumpheap -min 8500命令得到的那些对象选择一个有代表意义的对象,来看看里面究竟是什么东西:
0:011> !gcroot 0x3ef16668
Note: Roots found on stacks may be false positives. Run "!help gcroot" for
more info.
Scan Thread 9 OSTHread c8
Scan Thread 17 OSTHread 354
Scan Thread 18 OSTHread 12f8
Scan Thread 19 OSTHread 14d4
Scan Thread 20 OSTHread 10d4
Scan Thread 22 OSTHread f58
Scan Thread 10 OSTHread d2c
Scan Thread 11 OSTHread 1180
ESP:277eef4:Root:06942b80(*.ServiceImpls.SearchImpls.InformationSearchService)->
6e31491c(System.EventHandler`1[[*.ServiceInterfaces.SearchedEventArgs, *.ServiceInterfaces]])->
39f76528(System.Object[])->
4614dc40(System.EventHandler`1[[*.ServiceInterfaces.SearchedEventArgs, *.ServiceInterfaces]])->
4614db74(*.DataSources.InformationSearchDataView)->
3ef5d91c(*.DataSources.InformationSearchDataSource)->
3ef57f38(ASP.product_sortproductlist_aspx)->
3ef02d08(System.Web.HttpContext)->
3ef35a60(System.Web.CachedPathData)->
3ef57d08(System.Web.Configuration.HandlerMappingMemo)->
3ef43e10(System.Web.Configuration.HttpHandlerAction)->
3ef1fde0(System.Configuration.RuntimeConfigurationRecord)->
3ef0e920(System.Configuration.RuntimeConfigurationRecord)->
3ef0ef18(System.Collections.Hashtable)->
3ef15af8(System.Collections.Hashtable+bucket[])->
3ef1e6bc(System.Configuration.SectionRecord)->
3ef1e6e4(System.Configuration.SectionInput)->
3ef1e67c(System.Configuration.SectionXmlInfo)->
3ef16668(System.String)
还真的是页面实例,但是引用链条的根部是在,SearchImpls.InformationSearchService 这个类的实例上。也就是说,只要这个实例无法被释放,那么被引用的InformationSearchDataSource就无法被释放,由于InformationSearchDataSource本身又有Page的引用,而Page又被其它的UI控件和上下文对象所引用。造成了,整个页面实例的对象都无法被释放?那么是什么原因造成了这样的一个引用链条无法被分离呢?
突破口就在 6e31491c(System.EventHandler`1[[*.ServiceInterfaces.SearchedEventArgs, *.ServiceInterfaces]])-> 上面。它是一个事件代理?那么为什么它有这么大的能耐呢?要清楚的解释这个问题,我还得解释一下我这个类的结构。
我这里是有使用NBear.IoC,设计一个搜索接口,名为ISearchService。这个接口上有OnSearching和OnSearched事件。专门用于挂载,搜索之前和搜索之后的事件代理。然后在DataSource中是这样使用这个接口服务的:
NBear.IoC.ServiceFactory.GetService<ISearchService>().OnSearched += new EventHandler(Searched);
这里的Searched是DataSource里的一个实例函数。又由于NBear.IoC中,对组件默认使用的单实例的方式。解释到这里,不知道你知道问题的大概了没?
由于asp.net对每个用户访问的请求,都会对页面进行实例化。一个页面可能会被创建很多很多的实例。不同的页面实例,会创建不同的DataSource实例,而不同的DataSource实例都往ISearchService的接口服务上挂自己实例的Searched方法。而又因为NBear.IoC对组件使用的是单实例的方式的,也就是说,随着时间的推进,ISearchService的接口服务的Searched事件会被挂上很多DataSource不同实例的Searched代理。而如果ISearchService的接口服务不释放,就意味的这些事件代理的不会变成不可达对象,而这个代理的链条一直就这么连接着,直接地就影响了DataSource实例的释放,进而影响Page实例的释放。
OK,怎么感觉就像是在绕圈子呢?如果这时你还是一头雾水的话,建议你去了解一下.NET拖管对象的回收机制。建议读《.NET 框架程序设计修订版》。
到这里,我的分析就基本结束了。剩下的工作就是验证分析的结果了。修改了程序,升级上去以后。程序的起步价要求明显降低了很多,而且内存的上升步长单位也比较少,过一段时间稳定后,也基本就在260多M上下浮动。总之一切看起来都那么美!
从这个Case中,我总结出了自己的东西。对于单实例的设计,比如单件实例模式。我们始终要把一点记在心里,一旦单件实例创建后。它就会随着进程永驻内存。它所引用到的所有对象也会跟着它一样存在和消失,应用程序域未被卸载前,GC就不能把它们咋地!
这篇加上前面两篇,就是我这两天初试WinDbg的“辉煌”结果。我很惊奇,我在很多概念都还没有了解清楚,不知道都有哪些命令的情况,也能利用WinDbg来解决我的实际问题。所以通过这三篇博客,我希望能给一个像我这样的新手一个启发。我们也能自己排错了。但这只是开始,如果WinDbg都像我说这样简单的话,那微软就不用专门有这样的团队了。呵呵
前两篇的链接:
阿不