BitArray雖好,但請不要濫用,又一次線上內存暴增排查

一:背景

1. 講故事

前天寫了一篇大內存排查在園子裏挺火,這是作自媒體最開心的事拉,乾脆再來一篇知足你們胃口,上個月我寫了一篇博客提到過使用bitmap對原來的List<CustomerID>進行高強度壓縮,將原來的List內存壓縮了將近106倍,可是bitmap不是一味的好,你必須在正確的場景中使用,而不是閉着眼睛濫用,bitmap在C#中對應的集合是BitArray。mongodb

好像劇透了😄😄😄,結果就是BitArray的濫用致使內存小10G的漲跌,不過這種東西重點仍是看解決思路,寫給之後的本身,可不能讓這可貴的實踐經驗蒸發啦~~~數組

二:解決思路

1. 一看託管堆

看託管堆雖然是一個好主意,但也不是每次都湊效,畢竟形成內存暴漲暴跌的緣由各類各樣,就像人感冒有風寒,風熱和病毒性,對吧😁,仍是使用老命令: !dumpheap -stat -min 102400 ,在託管堆上找大於100M的對象。緩存

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

去掉一些敏感類後,再觀察好像沒有特別顯眼的集合,像 System.Int64[] ,System.Linq.Set1+Slot[[System.Int64, mscorlib]][] 通常都是用做其餘集合的內存存儲,不少時候用!gcroort 抓不出來,最大的反而是Free列,有347個碎片,高達 3.5G,說明此時的大對象堆是一塌糊塗啊,要是GC能幫忙壓縮一下該多好😄。線程

2. 查看每個線程的調用棧

先慣性的偷窺一下程序中有多少個線程。3d

0:000> !threads
ThreadCount:      74
UnstartedThread:  0
BackgroundThread: 72
PendingThread:    0
DeadThread:       0
Hosted Runtime:   no
                                                                                                        Lock  
       ID OSID ThreadOBJ           State GC Mode     GC Alloc Context                  Domain           Count Apt Exception
   0    1 2958 000001cc882e5a40    2a020 Preemptive  0000000000000000:0000000000000000 000001cc882d8db0 1     MTA 
   2    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     MTA 
   6   40 2a64 000001cca3048f10  1020220 Preemptive  0000000000000000:0000000000000000 000001cc882d8db0 0     Ukn (Threadpool Worker) 
   7   46  e34 000001cca311c390  202b220 Preemptive  0000000000000000:0000000000000000 000001cc882d8db0 0     MTA 
   8   47 27d8 000001cca3115e00    2b220 Preemptive  0000000000000000:0000000000000000 000001cc882d8db0 0     MTA 

...

能夠看到當前有74個線程,後臺線程有72個,接下來用 ~*e !clrstack 查看每一個託管線程都在作什麼,因爲內容太多,我就節選一下了哈。code

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)

發現一個奇怪的現象,有4個線程 29,30,40,53Monitor.ObjWait 處卡住了,從調用棧來看這四個傢伙正在準備向Mongodb批量插入數據[InsertBatch],此時應該有其餘的一個線程先行獲取到了lock正在作InsertBatch,這四個線程在等待,如何以爲抽象了,我畫一張圖:orm

3. 尋找insertbatch處的集合

這裏我就拿 30號線程說事,從上圖調用棧中你應該看到一個System.Collections.Generic.IEnumerable1<System.__Canon>,從IEnumerable中能夠猜想實現類應該是List或者HashSet這樣的集合,接下來用 !dso 把30號線程棧上的對象所有dump出來。對象

從圖中看應該就是這個 List<xxx.Common.GroupConditionCustomerIDCacheModel>,而後用!objsize!do 給List量個尺寸而且dump一下。blog

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個,說明重點都在 _items 數組裏啦,接下里用 da 把第一個item拿出來解剖下。圖片

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] 000001d3fa581540
    Name:        DataMipCRM.Common.GroupConditionCustomerIDCacheModel
    MethodTable: 00007ffdab955740
    EEClass:     00007ffdab94b9e8
    Size:        64(0x40) bytes
    File:        D:\LuneceService\DataMipCRM.Common.dll
    Fields:
                      MT    Field   Offset                 Type VT     Attr            Value Name
        00007ffdaac69258  4000589       28     ...oDB.Bson.ObjectId      1     instance     000001d3fa581568     <_id>k__BackingField
        00007ffe094e9288  400058a       20             System.Int32      1     instance                 1901     <ShopId>k__BackingField
        00007ffe094e6948  400058b        8            System.String      0     instance     000001d3f7154070     <GroupConditionHasCode>k__BackingField
        00007ffe094e6948  400058c       10            System.String      0     instance     000001cca7b46ac0     <unit>k__BackingField
        00007ffe094f1cb0  400058d       18     ...lections.BitArray      0     instance     000001d3fa581580     <customeridArray>k__BackingField

從最後一行的Type列能夠看到有一個 BitArray 類,仍是同樣,先量個尺寸再打出來。

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中看,這個bitarray佔用 956008/1024/1024 = 0.91M,真tmd的大,看bit位有 764w 個,說明有一個CustomerID=7647524-1放在這裏面,問題基本上就算找到了,如今終於知道內存爲何這麼大,算一下就出來了。

四:總結

最後去問了下搬磚的爲何這麼寫,是由於給客戶展現的一張報表,爲了加速。。。將每個點上的人羣保存起來放在BitArray上而後進入Monogdb緩存,這樣客戶後續選擇某一個點進行 下鑽 操做的話,能夠直接從mongodb中將BitArray的人羣復原出來,免去程序重複計算之苦,由於每一個點的人羣具備排他性,落在每一個點上的人可能只有幾十,幾百,幾千,而恰恰這家客戶有800w之多,天然這個CustomerID就特別大了,更不巧的就用了bitArray存少許的大數字,這些趕在一塊兒以後,就是一個典型的濫用bitarray,您說的?


如您有更多問題與我互動,掃描下方進來吧~


圖片名稱
相關文章
相關標籤/搜索