一:背景

1. 讲故事

前天写了一篇大内存排查在园子里挺火,这是做自媒体最开心的事拉,干脆再来一篇满足大家胃口,上个月我写了一篇博客提到过使用bitmap对原来的List<CustomerID>进行高强度压缩,将原来的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,53Monitor.ObjWait 处卡住了,从调用栈来看这四个家伙正在准备向Mongodb批量插入数据[InsertBatch],此时应该有其他的一个线程先行获取到了lock正在做InsertBatch,这四个线程在等待,如何觉得抽象了,我画一张图:

3. 寻找insertbatch处的集合

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

从图中看应该就是这个 List<xxx.Common.GroupConditionCustomerIDCacheModel>,然后用!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:\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,您说的?


如您有更多问题与我互动,扫描下方进来吧~


BitArray虽好,但请不要滥用,又一次线上内存暴增排查的更多相关文章

  1. 2019年春季学期第二周作业 基础作业 请在第一周作业的基础上,继续完成:找出给定的文件中数组的最大值及其对应的最小下标(下标从0开始)。并将最大值和对应的最小下标数值写入文件。 输入: 请建立以自己英文名字命名的txt文件,并输入数组元素数值,元素值之间用逗号分隔。 输出 在不删除原有文件内容的情况下,将最大值和对应的最小下标数值写入文件

    ~~~ include<stdio.h> include<stdlib.h> int main() { FILE*fp; int i=0,max=0,j=0,maxb=0; i ...

  2. 线上Java程序占用 CPU 过高,请说一下排查方法?

    我是风筝,公众号「古时的风筝」,一个兼具深度与广度的程序员鼓励师,一个本打算写诗却写起了代码的田园码农! 文章会收录在 JavaNewBee 中,更有 Java 后端知识图谱,从小白到大牛要走的路都在 ...

  3. php项目,cpu暴增问题查找

    背景: 前几天通过WordPress上线一个应用(前后台部署分离,后台走内网内部使用,前台做了全站缓存对外使用). 今天访问后台应用发现开始报504,一段时间后全部504. 解决方案: 登录容器发现容 ...

  4. bootstrap + requireJS+ director+ knockout + web API = 一个时髦的单页程序

    也许单页程序(Single Page Application)并不是什么时髦的玩意,像Gmail在很早之前就已经在使用这种模式.通常的说法是它通过避免页面刷新大大提高了网站的响应性,像操作桌面应用程序 ...

  5. 【转】iOS学习之容易造成循环引用的三种场景

    ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...

  6. java基础之 GC

    Java程序员在编码过程中通常不需要考虑内存问题,JVM经过高度优化的GC机制大部分情况下都能够很好地处理堆(Heap)的清理问题.以至于许多Java程序员认为,我只需要关心何时创建对象,而回收对象, ...

  7. 【原】iOS容易造成循环引用的三种场景,就在你我身边!

    ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是——循环引用.循环引用可以简单理解为A引用了B,而B又引用了A,双方都同 ...

  8. iOS容易造成循环引用的三种场景

    iOS容易造成循环引用的三种场景  ARC已经出来很久了,自动释放内存的确很方便,但是并非绝对安全绝对不会产生内存泄露.导致iOS对象无法按预期释放的一个无形杀手是--循环引用.循环引用可以简单理解为 ...

  9. 内存恶鬼drawRect

    标题有点吓人,但是对于drawRect的评价倒是一点都不过分.在平日的开发中,随意覆盖drawRect方法,稍有不慎就会让你的程序内存暴增.下面我们来看一个例子. 去年的某天午后,北京的雾霾依旧像现在 ...

随机推荐

  1. 【转载】pyinstaller的使用和几个坑

    1.-w是不显示命令窗口,  -i 图标文件的路径  这是改变图标的,但是我发现只能改变任务栏里的和命令窗口的图标,并不能改变exe文件的图标.另外这些参数要加载pyinstaller和路径中间. 2 ...

  2. Crash日志解析

    当应用程序崩溃时,会创建一个崩溃报告,这对于了解导致崩溃的原因非常有用.本文档包含有关如何表示,理解和解释崩溃报告的基本信息. 1.介绍 2.获取崩溃和低内存报告 3.象征性的奔溃报告 1.位码(bi ...

  3. servlet温故知新

    重新学习了一遍servlet-api的文档,做一些记录. 有道云的笔记直接复制到博客上格式会乱,因此直接放上有道云的链接:http://note.youdao.com/noteshare?id=915 ...

  4. 图论--最短路--SPFA模板(能过题,真没错的模板)

    [ACM常用模板合集] #include<iostream> #include<queue> #include<algorithm> #include<set ...

  5. MySQL Windows 环境安装

    1.下载 MySQL Windows 安装包 下载地址:https://downloads.mysql.com/archives/installer/ 我这个是 MySQL 5.7 版本 2.直接双击 ...

  6. vue element select多选回显

    我们经常在使用 Element组件里面的 select多选 场景:添加账号的时候需要选择可见分公司(分公司为多选),添加成功之后可以编辑,需要回显添加时所提交的分公司 代码如下: 多选框: data( ...

  7. Azure AD(一)入门认识

    一,引言(吹水) 距离上一次介绍Azure Functions的相关博文以及过期快一个月了,本来早早都想好已经规划好的Azure的相关的学习的路线,无奈还是由于自己文笔不好以及自身太懒,导致博文没有更 ...

  8. bootstrap 怎么制作好看的表格

    bootstrap 怎么制作表格 bootstrap 制作表格带有图文形式.主要知识点有以下几点 第一点肯定是写出一个普通的表格,这一点可以去菜鸟复制它的案例.添加tr和td就可以了 在表格放入图片加 ...

  9. P2002 消息扩散(缩点)

    描述:https://www.luogu.com.cn/problem/P2002 有n个城市,中间有单向道路连接,消息会沿着道路扩散,现在给出n个城市及其之间的道路,问至少需要在几个城市发布消息才能 ...

  10. JDK基本库概述

    看脚下,不断行,莫存顺逆. 剖析java的哪些源码 目前主要是java基本库的一些源码的分析,jvm工具的使用等等,后续可能还会结合hotspot源码来分析jvm原理,当然,这是一个比较高级的主题,根 ...