记一次 .NET 某打印服务 非托管内存泄漏

一:背景

1. 讲故事

前段时间有位朋友在微信上找到我,说他的程序出现了内存泄漏,能不能帮他看一下,这个问题还是比较经典的,加上好久没上非托管方面的东西了,这篇就和大家分享一下,话不多说,上 WinDbg 说话。

二:WinDbg 分析

1. 到底是哪里的泄漏

好的开始就是成功的一半,否则就南辕北辙了,对吧,还是用经典的 !address -summary 看一下内存排布情况。

0:000> !address -summary--- Usage Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
Heap                                   1935          553b3000 (   1.332 GB)  70.57%   66.59%
Image                                  1022           c306000 ( 195.023 MB)  10.09%    9.52%
<unknown>                              1202           c09d000 ( 192.613 MB)   9.97%    9.41%
Stack                                   541           b280000 ( 178.500 MB)   9.24%    8.72%
Free                                   1158           73ab000 ( 115.668 MB)            5.65%
TEB                                     180            20f000 (   2.059 MB)   0.11%    0.10%
Other                                     8             5d000 ( 372.000 kB)   0.02%    0.02%
PEB                                       1              3000 (  12.000 kB)   0.00%    0.00%--- State Summary ---------------- RgnCount ----------- Total Size -------- %ofBusy %ofTotal
MEM_COMMIT                             3077          643c6000 (   1.566 GB)  83.00%   78.31%
MEM_RESERVE                            1812          1487f000 ( 328.496 MB)  17.00%   16.04%
MEM_FREE                               1158           73ab000 ( 115.668 MB)            5.65%

从卦中可以看出,当前 MEM_COMMIT = 1.56 G, 并且 Heap= 1.3 G,既然超出了朋友的预期,很明显这是一个非托管内存泄漏,既然 NTHeap 出现了泄漏,那就挖一下看看,使用 !heap -s 观察一下各个heap句柄。

0:000> !heap -s************************************************************************************************************************NT HEAP STATS BELOW
************************************************************************************************************************
LFH Key                   : 0xbb72f2a3
Termination on corruption : DISABLEDHeap     Flags   Reserv  Commit  Virt   Free  List   UCR  Virt  Lock  Fast (k)     (k)    (k)     (k) length      blocks cont. heap 
-----------------------------------------------------------------------------
00770000 00000002   16576   9716  16364     33   195     5    0      0   LFH
006f0000 00001002    1292    148   1080     11     4     2    0      0   LFH
00a80000 00001002    3336   1972   3124     88    25     3    0      0   LFH
02460000 00001002      60      4     60      0     1     1    0      0      
023b0000 00041002      60      4     60      2     1     1    0      0      
02450000 00001002     272     24     60      1     3     1    0      0   LFH
04a40000 00041002    1292     80   1080      8     4     2    0      0   LFH
06e90000 00001002   64180  56660  63968   1434   473     9  624      7   LFH
09dc0000 00001002      60     12     60      3     2     1    0      0      
0a500000 00001002    7428   3772   7216     43    35     4    0      0   LFH
-----------------------------------------------------------------------------

从卦中的 Commit 列来看,内存占用都不大,最大的也不过 56M ,如果经验丰富的话,你会发现 Virt blocks 高达 624 个,明白 ntheap 的朋友应该知道,凡是大于 512kheapentry 都会单独安排到 VirtualAllocdBlocks 数组中,可以用 dt ntdll!_HEAP 06e90000 给show出来。

0:000> dt ntdll!_HEAP 06e90000...+0x05c VirtualMemoryThreshold : 0xfe00+0x09c VirtualAllocdBlocks : _LIST_ENTRY [ 0x6ea4000 - 0x7c0d0000 ]...

为了更好的输出 VirtualAllocdBlocks 数组,我们用 windbg 自带的 heap 分析命令。

0:000> !heap 06e90000 -m
Index   Address  Name      Debugging options enabled8:   06e90000 Segment at 06e90000 to 06e9f000 (0000f000 bytes committed)Segment at 078f0000 to 079ef000 (000ff000 bytes committed)Segment at 08870000 to 08a6f000 (001ff000 bytes committed)Segment at 0ec60000 to 0f05f000 (003f9000 bytes committed)Segment at 18660000 to 18e5f000 (007fa000 bytes committed)Segment at 26b20000 to 27aef000 (00fc0000 bytes committed)Segment at 45320000 to 462ef000 (00fcf000 bytes committed)Segment at 65bf0000 to 66bbf000 (008bf000 bytes committed)Flags:                00001002ForceFlags:           00000000Granularity:          8 bytesSegment Reserve:      03f70000Segment Commit:       00002000DeCommit Block Thres: 00000800DeCommit Total Thres: 00002000Total Free Size:      0002cd56Max. Allocation Size: 7ffdefffLock Variable at:     06e90258Next TagIndex:        0000Maximum TagIndex:     0000Tag Entries:          00000000PsuedoTag Entries:    00000000Virtual Alloc List:   06e9009c06ea4000: 00200000 [commited 201000, unused 1000] - busy (b)070b2000: 00200000 [commited 201000, unused 1000] - busy (b)079f4000: 00200000 [commited 201000, unused 1000] - busy (b)07c0f000: 00200000 [commited 201000, unused 1000] - busy (b)0802b000: 00200000 [commited 201000, unused 1000] - busy (b)08238000: 00200000 [commited 201000, unused 1000] - busy (b)08444000: 00200000 [commited 201000, unused 1000] - busy (b)0865f000: 00200000 [commited 201000, unused 1000] - busy (b)0e20f000: 00200000 [commited 201000, unused 1000] - busy (b)0e42b000: 00200000 [commited 201000, unused 1000] - busy (b)0e635000: 00200000 [commited 201000, unused 1000] - busy (b)0e841000: 00200000 [commited 201000, unused 1000] - busy (b)0c661000: 00200000 [commited 201000, unused 1000] - busy (b)0c87e000: 00200000 [commited 201000, unused 1000] - busy (b)0ca8b000: 00200000 [commited 201000, unused 1000] - busy (b)0ea56000: 00200000 [commited 201000, unused 1000] - busy (b)0f062000: 00200000 [commited 201000, unused 1000] - busy (b)0f275000: 00200000 [commited 201000, unused 1000] - busy (b)...

从卦中可以看到大量的 commited 201000, unused 1000 ,这里的 0x201000 转换一下大概就是 2M,以经验来说,这 2M 大概就是 pdf,image,bitmap 等这些玩意了,由于没有开启 pageheap 或 ust,没法追踪到底是什么东西分配的,到这里就没法进展下去了。

2. 到底是谁分配的 2M 数据

首先能进入 VirtualAllocdBlocks 数组自然是高层调用了 HeapAlloc 这类API,同时这个数据量高度怀疑是 Bitmap,Pdf 之类的大文件,很大可能是托管代码做了什么导致这个资源没有释放,接下来使用 !dumpheap -stat 看下托管堆。

0:000> !dumpheap -stat
Statistics:MT    Count    TotalSize Class Name
...
09ae7e48      627        15048 System.Drawing.Bitmap
6b267040      178       366680 System.Decimal[]
6b2cb4a0     1850       601588 System.String[]
6b2cdd14     1379       638190 System.Byte[]
6b2cac14    15919      1146764 System.String
09aec720    66332      1326640 System.Drawing.FontFamily
09ae8590    66074      2907256 System.Drawing.Font
Total 289300 objects

从卦中看,System.Drawing.Font 居然高达 6w 个,而且 System.Drawing.Bitmap 和 heap 上的 624 也非常接近,看样子就是 Bitmap 啦,那为什么这个 Bitmap 没有善终呢?可以用 !frq -stat 观察下终结器队列的 Freachable Queue 情况。

0:000> !frq -stat
Freachable Queue:Count      Total Size   Type
---------------------------------------------------------152            3648   System.Data.Odbc.CNativeBuffer76            2128   System.Data.Odbc.OdbcConnectionHandle77            1540   System.Transactions.SafeIUnknown76            1824   System.Data.Odbc.OdbcStatementHandle2432          145920   System.Windows.Forms.Control+ControlNativeWindow304            7296   System.Drawing.Bitmap66062         2906728   System.Drawing.Font258            5160   System.Drawing.FontFamily308            9856   System.Drawing.Graphics308            3696   System.Windows.Forms.ImageList+NativeImageList1              12   System.Drawing.Text.InstalledFontCollection12             240   System.Threading.ThreadPoolWorkQueueThreadLocals1              20   System.Security.Cryptography.SafeKeyHandle1              20   Microsoft.Win32.SafeHandles.SafeWaitHandle6             120   Microsoft.Win32.SafeHandles.SafeRegistryHandle12             624   System.Threading.Thread1577           69388   System.Threading.ReaderWriterLock1              20   System.Security.Cryptography.SafeProvHandle71,664 objects, 3,158,240 bytes

我去,这个可终结队列居然高达 7.1w ,这是很有问题的,大概率当前的终结器线程瓦特了,接下来追查下 终结器线程 此时正在做什么 ?

0:000> !t
ThreadCount:      107
UnstartedThread:  0
BackgroundThread: 93
PendingThread:    0
DeadThread:       12
Hosted Runtime:   noLock  ID OSID ThreadOBJ    State GC Mode     GC Alloc Context  Domain   Count Apt Exception0    1 138ac 0079fef0     26020 Preemptive  00000000:00000000 00798f38 1     STA 2    2 12b08 007adac0     2b220 Preemptive  00000000:00000000 00798f38 0     MTA (Finalizer) ...0:000> ~2s
eax=00000001 ebx=00000000 ecx=00000000 edx=00000000 esi=00000001 edi=00000001
eip=777b2f8c esp=0466eaf4 ebp=0466ec84 iopl=0         nv up ei pl nz na po nc
cs=0023  ss=002b  ds=002b  es=002b  fs=0053  gs=002b             efl=00000202
ntdll!NtWaitForMultipleObjects+0xc:
777b2f8c c21400          ret     14h
0:002> k# ChildEBP RetAddr      
00 0466ec84 762fc753     ntdll!NtWaitForMultipleObjects+0xc
01 0466ec84 7695d9aa     KERNELBASE!WaitForMultipleObjectsEx+0x103
02 0466ed34 7695c564     combase!MTAThreadWaitForCall+0x1ca [onecore\com\combase\dcomrem\channelb.cxx @ 7234] 
03 0466edc0 769a9923     combase!MTAThreadDispatchCrossApartmentCall+0xf4 [onecore\com\combase\dcomrem\chancont.cxx @ 229] 
04 (Inline) --------     combase!CSyncClientCall::SwitchAptAndDispatchCall+0x9e4 [onecore\com\combase\dcomrem\channelb.cxx @ 5856] 
05 0466efa0 769ab739     combase!CSyncClientCall::SendReceive2+0xad3 [onecore\com\combase\dcomrem\channelb.cxx @ 5459] 
06 (Inline) --------     combase!SyncClientCallRetryContext::SendReceiveWithRetry+0x29 [onecore\com\combase\dcomrem\callctrl.cxx @ 1542] 
07 (Inline) --------     combase!CSyncClientCall::SendReceiveInRetryContext+0x29 [onecore\com\combase\dcomrem\callctrl.cxx @ 565] 
...

从上面的 MTAThreadDispatchCrossApartmentCall 可知,这又是一个经典的 COM 释放问题导致终结器线程卡死。。。接下来仔细看下 线程列表的 STA 情况,可以发现有大量的线程是 STA 模式。

d3c8587fb13b48d8805cf90aa4f592cd.png

接下来就是将结果告诉朋友,为什么开的线程都是 STA 套件模式。

三:总结

总的来说,这次内存泄漏的原因在于朋友开了 STA 模式的线程,导致终结器线程卡死,进而导致 Bitmap 分配之后无法释放,最终引发非托管泄漏。

这个dump告诉我们,不要放弃,一定可以在绝望中找到希望。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/282806.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

android静态方法如何测试,android – 如何使用mock()和spy()测试静态方法

通常情况下,如果你最终使用PowerMock,这是一个很好的迹象,表明你最有可能是错误的方式.如果不是直接引用毕加索,而是创建一个组件,它的职责是加载图像,让我们说类ImageLoader.这会给你什么&#xff1f;>关注点分离&#xff1a;如果明天你决定转移到Glide,你不应该改变你使用…

mysql经典的8小时问题-wait_timeout

2019独角兽企业重金招聘Python工程师标准>>> 前段时间 现网突然频繁报出 连接不上数据库&#xff0c;偶滴的妖孽&#xff0c;其他地方都是用mysql&#xff0c;也没遇到这个问题呀。 java.io.EOFExceptionat at com.mysql.jdbc.MysqlIO.readFully(MysqlIO.java:1913…

Chrome DevTools — Network

记录网络请求 默认情况下&#xff0c;只要DevTools在开启状态&#xff0c;DevTools会记录所有的网络请求&#xff0c;当然&#xff0c;记录都是在Network面板展示的。 停止记录网络请求 点击Stop recording network log红色图标&#xff0c;当它变为灰色时&#xff0c;表示DevT…

Blazor University 中文版网站已上线

在学习 Blazor 的过程中&#xff0c;找到了一个网站 Blazor University&#xff08;https://blazor-university.com&#xff09;。发现网站内容非常详实&#xff0c;正像首页所说的&#xff1a;通过浏览本网站中的信息&#xff0c;我打算带您从完全的新手到Blazor的所有方面的专…

android:paddingtop 百分比,相对层中的百分比宽度

相对层中的百分比宽度我正在为登录进行表单布局。Activity在我的Android应用程序中。下面的图片是我希望它看起来的样子&#xff1a;我能够通过以下方式实现这个布局XML..问题是&#xff0c;这有点麻烦。我不得不对主机EditText的宽度进行硬编码。具体而言&#xff0c;我必须具…

MySQL 查看表结构简单命令

一、简单描述表结构&#xff0c;字段类型 desc tabl_name; 显示表结构&#xff0c;字段类型&#xff0c;主键&#xff0c;是否为空等属性&#xff0c;但不显示外键。 例如&#xff1a;desc table_name 二、查询表中列的注释信息 select * from information_schema.columns wher…

简单获取任意app的URL Schemes

简单说明 最近业务需要&#xff0c;一直在查询App的scheme相关信息&#xff0c;找到一种比较可靠的方法&#xff0c;分享给大家 步骤如下&#xff1a; 在电脑上使用iTunes下载那个app下载完后&#xff0c;在itunes里点击这个app&#xff0c;选择->Show in Finder&#xff0c…

Java中short、int、long、float、double的取值范围

一、基本数据类型的特点&#xff0c;位数&#xff0c;最大值和最小值。1、基本类型&#xff1a;short 二进制位数&#xff1a;16 包装类&#xff1a;java.lang.Short 最小值&#xff1a;Short.MIN_VALUE-32768 &#xff08;-2的15此方&#xff09;最大值&#xff1a;Short.MAX_…

.Net之接口文档精度丢失处理

目的最近两天在给朋友讲解如何使用ajax调用接口时候&#xff0c;我发现我用swagger调用接口返回的long类型的数据最后几位都变成了0(例如&#xff1a;6974150586715898000)&#xff0c;本来是以为sqlite数据库不支持long类型导致我存进去的数据出了问题&#xff0c;然后我使用接…

android 访问sqlite,android中访问已有的sqlite数据库

推荐文章每天进步记录一点点话说经常性的操作svn出现各种问题,而度娘一直帮倒忙,是不是很手足无措.有时问题还是要记录下来的.说不定还会有惊喜. 昨天遇到个问题,搜索了一下,发现第一条就是自己写的.惊呆我了,更惊呆我的是,我是在csdn写的,为什么在别的网站看到,完全一模一样..…

Dnslog在SQL注入中的利用

参考文献&#xff1a;www.anquanke.com/post/id/98096https://bbs.pediy.com/thread-223881.htm DNSlog在Web攻击的利用 在某些无法直接利用漏洞获得回显的情况下&#xff0c;但是目标可以发起DNS请求&#xff0c;这个时候就可以通过DNSlog把想获得的数据外带出来。 常用情况 S…

让泛型的思维扎根在脑海——深刻理解泛型

1.前言往往一些刚接触C#编程的初学者&#xff0c;对于泛型的认识就是直接跳到对泛型集合的使用上&#xff0c;虽然微软为我们提供了很多内置的泛型类型&#xff0c;但是如果我们只是片面的了解调用方式&#xff0c;这会导致我们对泛型盲目的使用。至于为什么要使用泛型&#xf…

fgetcsv()函数

fgetcsv()函数。fgetcsv()函数可以读取指定文件的当前行&#xff0c;使用CSV格式解析出字段&#xff0c;并返回一个包含这些字段的数组。语法格式如下&#xff1a;array fgetcsv(resource $handle [, int $length [, string $delimiter [, string $enclosure[,string $escape]]…

android 系统ui修改器,分享两个效果 - Android 系统 UI 管理

SystemUIManage.gifDimming the System Bars (沉浸模式)知乎 和 Medium 中都使用到了这个效果&#xff0c;作为沉浸式阅读模式。// This example uses decor view, but you can use any visible view.View decorView getWindow().getDecorView();int uiOptions View.SYSTEM_U…

打游戏要存进度-备忘录模式

打游戏要存进度-备忘录模式 学习自 《大话设计模式》 备忘录模式漫谈 备忘录的这种设计思想是非常常见的&#xff0c;比如说围棋游戏的悔棋&#xff0c;绘图软件的撤销功能等等&#xff0c;都或多或少的使用了备忘录模式来处理对象的状态。 备忘录(Memento): 在不破坏封装性的前…

(10.1)Python学习笔记二

1、在项目工程中要模块化测试一个开发的功能&#xff0c;在测试通过后交付给项目组其他人员继续开发。要保证代码开发的性能和效率以及可扩展性。 2、项目工程中的文件夹分类要功能模块明确清晰&#xff0c;在python中引入某一个 文件夹下的文件可以使用如下方式&#xff1a; t…

利用lay-ui结合ajax实现分页功能(不借助框架,简单易懂)

效果图: 1.创建html页面 01.html(前台文件) 2.创建index.php(后台文件) ------------------热身结束,开始正式分页之旅------------------ 3.在html页面中引入layui需要用到的css以及js,还有我们自己额外需要用到的jquery 4.在html文件中,将基本的分页栏显示出来 5.好啦,htm…

Linux系统压缩及解压缩

Linux系统解压缩概述&#xff1a;本篇将介绍Linux系统中的压缩和解压缩的工具&#xff0c;以及归档工具&#xff08;tar&#xff0c;cpio&#xff09;compress/uncompress&#xff1a;对应 .Z 结尾的压缩格式文件&#xff1b;gzip/gunzip&#xff1a;其对应的是 .gz 结尾的压缩…

酷派手机android版本,系统版本迎来升级

系统版本迎来升级这个应该是两个版本之间最大但是却不那么直观的不同了&#xff0c;因为从TD版酷派大神F1采用的CoolLife UI 5.0版本&#xff0c;再到联通版酷派大神F1所搭载的CoolLife UI 5.5版本&#xff0c;它们之间经历了一个比较不错的升级。在图标ICON&#xff0c;功能设…

学习RUNOOB.COM进度一

了解MongoDB 由C语言编写的&#xff0c;是一个基于分布式文件存储的开源数据库系统。在高负载的情况下&#xff0c;添加更多的节点&#xff0c;可以保证服务器性能。 特点 面向文档&#xff0c;操作简单容易 设置任何索引&#xff0c;实现更快排序 本地或者网络创建数据镜像&am…