BitArray虽好,但请不要滥用,又一次线上内存暴增排查
一:背景
1. 講故事
前天寫(xiě)了一篇大內(nèi)存排查在園子里挺火,這是做自媒體最開(kāi)心的事拉,干脆再來(lái)一篇滿足大家胃口,上個(gè)月我寫(xiě)了一篇博客提到過(guò)使用bitmap對(duì)原來(lái)的List<CustomerID>進(jìn)行高強(qiáng)度壓縮,將原來(lái)的List內(nèi)存壓縮了將近106倍,但是bitmap不是一味的好,你必須在正確的場(chǎng)景中使用,而不是閉著眼睛濫用,bitmap在C#中對(duì)應(yīng)的集合是BitArray。
好像劇透了????????????,結(jié)果就是BitArray的濫用導(dǎo)致內(nèi)存小10G的漲跌,不過(guò)這種東西重點(diǎn)還是看解決思路,寫(xiě)給以后的自己,可不能讓這難得的實(shí)踐經(jīng)驗(yàn)蒸發(fā)啦~~~
二:解決思路
1. 一看托管堆
看托管堆雖然是一個(gè)好主意,但也不是每次都湊效,畢竟造成內(nèi)存暴漲暴跌的原因各種各樣,就像人感冒有風(fēng)寒,風(fēng)熱和病毒性,對(duì)吧????,還是使用老命令:?!dumpheap -stat -min 102400?,在托管堆上找大于100M的對(duì)象。
0:030> !dumpheap -stat -min 102400 Statistics:MT Count TotalSize Class Name 00007ffe094ec988 1 1438413 System.Byte[] 00007ffdab934c48 1 1810368 System.Collections.Generic.Dictionary`2+Entry[[System.Int32, mscorlib],[System.Collections.Generic.HashSet`1[[System.Int64, mscorlib]], System.Core]][] 00007ffe094e6948 1 2527996 System.String 00007ffdab9ace78 4 29499552 System.Collections.Generic.Dictionary`2+Entry[[System.Int64, mscorlib],[System.DateTime, mscorlib]][] 00007ffe094e4078 4 267342240 System.String[] 00007ffe094e9220 135 452683336 System.Int32[] 00007ffdab8cd620 123 1207931808 System.Collections.Generic.HashSet`1+Slot[[System.Int64, mscorlib]][] 00007ffe094c8510 185 1579292760 System.Int64[] 00007ffdab9516b0 154 1934622720 System.Linq.Set`1+Slot[[System.Int64, mscorlib]][] 000001cc882de970 347 3660623866 Free Total 1371 objects去掉一些敏感類后,再觀察好像沒(méi)有特別顯眼的集合,像?System.Int64[] ,System.Linq.Set1+Slot[[System.Int64, mscorlib]][]一般都是用作其他集合的內(nèi)存存儲(chǔ),很多時(shí)候用!gcroort?抓不出來(lái),最大的反而是Free列,有347個(gè)碎片,高達(dá)?3.5G,說(shuō)明此時(shí)的大對(duì)象堆是一塌糊涂啊,要是GC能幫忙壓縮一下該多好????。
2. 查看每一個(gè)線程的調(diào)用棧
先慣性的偷窺一下程序中有多少個(gè)線程。
0:000> !threads ThreadCount: 74 UnstartedThread: 0 BackgroundThread: 72 PendingThread: 0 DeadThread: 0 Hosted Runtime: noLock ID OSID ThreadOBJ State GC Mode GC Alloc Context Domain Count Apt Exception0 1 2958 000001cc882e5a40 2a020 Preemptive 0000000000000000:0000000000000000 000001cc882d8db0 1 MTA2 2 2358 000001cc883122c0 2b220 Preemptive 000001D41B132930:000001D41B1348A0 000001cc882d8db0 0 MTA (Finalizer)3 4 2204 000001cc883ae5d0 102a220 Preemptive 0000000000000000:0000000000000000 000001cc882d8db0 0 MTA (Threadpool Worker)5 7 278c 000001cca29d8ef0 202b220 Preemptive 000001D41AB53A98:000001D41AB55A58 000001cc882d8db0 1 MTA6 40 2a64 000001cca3048f10 1020220 Preemptive 0000000000000000:0000000000000000 000001cc882d8db0 0 Ukn (Threadpool Worker)7 46 e34 000001cca311c390 202b220 Preemptive 0000000000000000:0000000000000000 000001cc882d8db0 0 MTA8 47 27d8 000001cca3115e00 2b220 Preemptive 0000000000000000:0000000000000000 000001cc882d8db0 0 MTA...可以看到當(dāng)前有74個(gè)線程,后臺(tái)線程有72個(gè),接下來(lái)用?~*e !clrstack?查看每個(gè)托管線程都在做什么,由于內(nèi)容太多,我就節(jié)選一下了哈。
0:000> ~*e !clrstack OS Thread Id: 0x2d64 (29)Child SP IP Call Site 000000d908cfe698 00007ffe28646bf4 [GCFrame: 000000d908cfe698] 000000d908cfe768 00007ffe28646bf4 [HelperMethodFrame_1OBJ: 000000d908cfe768] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)OS Thread Id: 0x214c (30)Child SP IP Call Site 000000d90957e6e8 00007ffe28646bf4 [GCFrame: 000000d90957e6e8] 000000d90957e7b8 00007ffe28646bf4 [HelperMethodFrame_1OBJ: 000000d90957e7b8] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)OS Thread Id: 0x1dc0 (40)Child SP IP Call Site 000000d950ebe878 00007ffe28646bf4 [GCFrame: 000000d950ebe878] 000000d950ebe948 00007ffe28646bf4 [HelperMethodFrame_1OBJ: 000000d950ebe948] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object)OS Thread Id: 0x274c (53)Child SP IP Call Site 000000d9693fe518 00007ffe28646bf4 [GCFrame: 000000d9693fe518] 000000d9693fe5e8 00007ffe28646bf4 [HelperMethodFrame_1OBJ: 000000d9693fe5e8] System.Threading.Monitor.ObjWait(Boolean, Int32, System.Object) 000000d9693fe700 00007ffe09314d05 System.Threading.ManualResetEventSlim.Wait(Int32, System.Threading.CancellationToken) 000000d9693fe790 00007ffe0930d996 System.Threading.Tasks.Task.SpinThenBlockingWait(Int32, System.Threading.CancellationToken) 000000d9693fe800 00007ffe09c9b7a1 System.Threading.Tasks.Task.InternalWait(Int32, System.Threading.CancellationToken)發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,有4個(gè)線程?29,30,40,53?在?Monitor.ObjWait?處卡住了,從調(diào)用棧來(lái)看這四個(gè)家伙正在準(zhǔn)備向Mongodb批量插入數(shù)據(jù)[InsertBatch],此時(shí)應(yīng)該有其他的一個(gè)線程先行獲取到了lock正在做InsertBatch,這四個(gè)線程在等待,如何覺(jué)得抽象了,我畫(huà)一張圖:
3. 尋找insertbatch處的集合
這里我就拿?30號(hào)線程說(shuō)事,從上圖調(diào)用棧中你應(yīng)該看到一個(gè)System.Collections.Generic.IEnumerable1<System.__Canon>,從IEnumerable中可以猜測(cè)實(shí)現(xiàn)類應(yīng)該是List或者HashSet這樣的集合,接下來(lái)用?!dso?把30號(hào)線程棧上的對(duì)象全部dump出來(lái)。
從圖中看應(yīng)該就是這個(gè)?List<xxx.Common.GroupConditionCustomerIDCacheModel>,然后用!objsize??!do?給List量個(gè)尺寸并且dump一下。
0:030> !objsize 000001d3fa581518 sizeof(000001d3fa581518) = 1487587080 (0x58aac708) bytes (System.Collections.Generic.List`1[[DataMipCRM.Common.GroupConditionCustomerIDCacheModel, DataMipCRM.Common]]) 0:030> !do 000001d3fa581518 Name: System.Collections.Generic.List`1[[DataMipCRM.Common.GroupConditionCustomerIDCacheModel, DataMipCRM.Common]] MethodTable: 00007ffdab9557d0 EEClass: 00007ffe08eb22a0 Size: 40(0x28) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields:MT Field Offset Type VT Attr Value Name 00007ffe09478740 4001871 8 System.__Canon[] 0 instance 000001d3fa5b9bf8 _items 00007ffe094e9288 4001872 18 System.Int32 1 instance 1520 _size 00007ffe094e9288 4001873 1c System.Int32 1 instance 1520 _version 00007ffe094e6f28 4001874 10 System.Object 0 instance 0000000000000000 _syncRoot 00007ffe09478740 4001875 8 System.__Canon[] 0 static <no information>可以看出list占用?1487587080/1024/1024=1.4G,尼瑪這么大,嚇人哈,從_size看也就1520個(gè),說(shuō)明重點(diǎn)都在?_items?數(shù)組里啦,接下里用?da?把第一個(gè)item拿出來(lái)解剖下。
0:030> !da -length 1 -details 000001d3fa5b9bf8 Name: DataMipCRM.Common.GroupConditionCustomerIDCacheModel[] MethodTable: 00007ffdab955e10 EEClass: 00007ffe08eaaa00 Size: 16408(0x4018) bytes Array: Rank 1, Number of elements 2048, Type CLASS Element Methodtable: 00007ffdab955740 [0] 000001d3fa581540Name: DataMipCRM.Common.GroupConditionCustomerIDCacheModelMethodTable: 00007ffdab955740EEClass: 00007ffdab94b9e8Size: 64(0x40) bytesFile: D:\LuneceService\DataMipCRM.Common.dllFields:MT Field Offset Type VT Attr Value Name00007ffdaac69258 4000589 28 ...oDB.Bson.ObjectId 1 instance 000001d3fa581568 <_id>k__BackingField00007ffe094e9288 400058a 20 System.Int32 1 instance 1901 <ShopId>k__BackingField00007ffe094e6948 400058b 8 System.String 0 instance 000001d3f7154070 <GroupConditionHasCode>k__BackingField00007ffe094e6948 400058c 10 System.String 0 instance 000001cca7b46ac0 <unit>k__BackingField00007ffe094f1cb0 400058d 18 ...lections.BitArray 0 instance 000001d3fa581580 <customeridArray>k__BackingField從最后一行的Type列可以看到有一個(gè)?BitArray?類,還是一樣,先量個(gè)尺寸再打出來(lái)。
0:030> !objsize 000001d3fa581580 sizeof(000001d3fa581580) = 956008 (0xe9668) bytes (System.Collections.BitArray) 0:030> !do 000001d3fa581580 Name: System.Collections.BitArray MethodTable: 00007ffe094f1cb0 EEClass: 00007ffe08ead968 Size: 40(0x28) bytes File: C:\Windows\Microsoft.Net\assembly\GAC_64\mscorlib\v4.0_4.0.0.0__b77a5c561934e089\mscorlib.dll Fields:MT Field Offset Type VT Attr Value Name 00007ffe094e9220 40017e2 8 System.Int32[] 0 instance 000001d5320c6d18 m_array 00007ffe094e9288 40017e3 18 System.Int32 1 instance 7647524 m_length 00007ffe094e9288 40017e4 1c System.Int32 1 instance 2 _version 00007ffe094e6f28 40017e5 10 System.Object 0 instance 0000000000000000 _syncRoot從output中看,這個(gè)bitarray占用?956008/1024/1024 = 0.91M,真tmd的大,看bit位有?764w?個(gè),說(shuō)明有一個(gè)CustomerID=7647524-1放在這里面,問(wèn)題基本上就算找到了,現(xiàn)在終于知道內(nèi)存為什么這么大,算一下就出來(lái)了。
四:總結(jié)
最后去問(wèn)了下搬磚的為什么這么寫(xiě),是因?yàn)榻o客戶展示的一張報(bào)表,為了加速。。。將每一個(gè)點(diǎn)上的人群保存起來(lái)放在BitArray上然后進(jìn)入Monogdb緩存,這樣客戶后續(xù)選擇某一個(gè)點(diǎn)進(jìn)行 下鉆 操作的話,可以直接從mongodb中將BitArray的人群復(fù)原出來(lái),免去程序重復(fù)計(jì)算之苦,因?yàn)槊總€(gè)點(diǎn)的人群具有排他性,落在每個(gè)點(diǎn)上的人可能只有幾十,幾百,幾千,而偏偏這家客戶有800w之多,自然這個(gè)CustomerID就特別大了,更不巧的就用了bitArray存少量的大數(shù)字,這些趕在一起之后,就是一個(gè)典型的濫用bitarray,您說(shuō)的?
總結(jié)
以上是生活随笔為你收集整理的BitArray虽好,但请不要滥用,又一次线上内存暴增排查的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Abp v2.8.0发布 路线图
- 下一篇: [翻译]用于.NET Core的Wind