栏目分类:
子分类:
返回
名师互学网用户登录
快速导航关闭
当前搜索
当前分类
子分类
实用工具
热门搜索
名师互学网 > IT > 软件开发 > 后端开发 > C/C++/C#

BitArray虽好,但请不要滥用,又一次线上内存暴增排查

C/C++/C# 更新时间: 发布时间: IT归档 最新发布 模块sitemap 名妆网 法律咨询 聚返吧 英语巴士网 伯小乐 网商动力

BitArray虽好,但请不要滥用,又一次线上内存暴增排查

一:背景 1. 讲故事

前天写了一篇大内存排查在园子里挺火,这是做自媒体最开心的事拉,干脆再来一篇满足大家胃口,上个月我写了一篇博客提到过使用bitmap对原来的List进行高强度压缩,将原来的List内存压缩了将近106倍,但是bitmap不是一味的好,你必须在正确的场景中使用,而不是闭着眼睛滥用,bitmap在C#中对应的集合是BitArray。

好像剧透了,结果就是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. 查看每一个线程的调用栈

先惯性的偷窥一下程序中有多少个线程。


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 查看每个托管线程都在做什么,由于内容太多,我就节选一下了哈。

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,53 在 Monitor.ObjWait 处卡住了,从调用栈来看这四个家伙正在准备向Mongodb批量插入数据[InsertBatch],此时应该有其他的一个线程先行获取到了lock正在做InsertBatch,这四个线程在等待,如何觉得抽象了,我画一张图:

3. 寻找insertbatch处的集合

这里我就拿 30号线程说事,从上图调用栈中你应该看到一个System.Collections.Generic.IEnumerable1,从IEnumerable中可以猜测实现类应该是List或者HashSet这样的集合,接下来用 !dso 把30号线程栈上的对象全部dump出来。

从图中看应该就是这个 List,然后用!objsize ➕!do 给List量个尺寸并且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:WindowsMicrosoft.NetassemblyGAC_64mscorlibv4.0_4.0.0.0__b77a5c561934e089mscorlib.dll
Fields:
MT    Field   Offset   Type VT     Attr     Value Name
00007ffe09478740  4001871 8     System.__Canon[]  0 instance 000001d3fa5b9bf8 _items
00007ffe094e9288  400187218  System.Int32  1 instance      1520 _size
00007ffe094e9288  40018731c  System.Int32  1 instance      1520 _version
00007ffe094e6f28  400187410 System.Object  0 instance 0000000000000000 _syncRoot
00007ffe09478740  4001875 8     System.__Canon[]  0   static  


可以看出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:LuneceServiceDataMipCRM.Common.dll
    Fields:
 MT    Field   Offset   Type VT     Attr     Value Name
 00007ffdaac69258  400058928     ...oDB.Bson.ObjectId      1     instance     000001d3fa581568     <_id>k__BackingField
 00007ffe094e9288  400058a20      System.Int32      1     instance   1901     k__BackingField
 00007ffe094e6948  400058b 8     System.String      0     instance     000001d3f7154070     k__BackingField
 00007ffe094e6948  400058c10     System.String      0     instance     000001cca7b46ac0     k__BackingField
 00007ffe094f1cb0  400058d18     ...lections.BitArray      0     instance     000001d3fa581580     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:WindowsMicrosoft.NetassemblyGAC_64mscorlibv4.0_4.0.0.0__b77a5c561934e089mscorlib.dll
Fields:
MT    Field   Offset   Type VT     Attr     Value Name
00007ffe094e9220  40017e2 8System.Int32[]  0 instance 000001d5320c6d18 m_array
00007ffe094e9288  40017e318  System.Int32  1 instance   7647524 m_length
00007ffe094e9288  40017e41c  System.Int32  1 instance  2 _version
00007ffe094e6f28  40017e510 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,您说的?

转载请注明:文章转载自 www.mshxw.com
我们一直用心在做
关于我们 文章归档 网站地图 联系我们

版权所有 (c)2021-2022 MSHXW.COM

ICP备案号:晋ICP备2021003244-6号