WinDbg初始体验3,剪不断的铁链(OutOfMemory案例分析Ⅱ)

铁链总是很难剪断的,.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都像我说这样简单的话,那微软就不用专门有这样的团队了。呵呵

前两篇的链接:

WinDbg初始体验2,抓住不放的恶棍

WinDbg初始体验,问题来了怎么办?

阿不

你可能感兴趣的:(OutOfMemory)