.NET关于API 句柄泄漏分析

目录
  • 一:背景
    • 1. 讲故事
    • 2. 什么是句柄
  • 二: windbg 分析
    • 1. 看问题表象
    • 2. 查看句柄表
    • 3. 从托管堆找 OverlappedData 的徒孙辈
    • 4. 寻找最终答案
  • 三:总结

一:背景

1. 讲故事

上上周有位朋友找到我,说他的程序CPU和句柄都在不断的增长,无回头趋势,查了好些天也没什么进展,特加wx寻求帮助,截图如下:


看的出来这位朋友也是非常郁闷,出问题还出两个,气人哈,关于 cpu 爆高的问题我准备单独用一篇文章去侦读,这篇就先聊聊 句柄泄漏 的问题,毕竟写了20多篇,也是第一次聊到 handle 泄露,有点意思哈。

2. 什么是句柄

我个人理解的句柄:就是在托管层持有了一个对非托管层资源的引用,有了这个引用,我们就可以强制回收非托管资源,那什么是非托管资源? 我个人的理解是 gc 管不到的地方都是 非托管资源

通常包含这种句柄的类有: FileStream, Socket 等,如果大家有这个前置基础,接下来就可以用 windbg 去分析啦!

二: windbg 分析

1. 看问题表象

朋友从 任务管理器 中看到 handle =8770,那就说明程序中有 8770 个对非托管资源持有句柄,那怎么去看呢? 在说这个之前,大家有没有遇到这种现象,就是不管程序怎么泄漏,只要我们退出exe,那么所有的资源都会被神奇的 释放, 不管是托管资源还是非托管资源,这样说相信有很有朋友好奇这是怎么实现的??? 大家可以先想 10s。

揭晓答案啦! 简单的说, CLR 在内部维护了一张句柄表,当程序关闭时,CLR会强制释放句柄表中的所有句柄,那问题就简单了,既然 CLR 能触达,我相信通过 windbg 也能做到,对,就是通过 !gchandles 命令。

2. 查看句柄表

这里提醒一下,!gchandles 的作用域是 AppDomain,而不是 Process,接下来看一下命令输出:

0:000> !gchandles -stat
Statistics:
              MT    Count    TotalSize Class Name
...
00007ffccc1d2360        3       262280 System.Byte[]
00007ffccc116610       72       313224 System.Object[]
00007ffccc3814a0     8246       593712 System.Threading.OverlappedData
Total 10738 objects

Handles:
    Strong Handles:       312
    Pinned Handles:       18
    Async Pinned Handles: 8246
    Ref Count Handles:    1
    Weak Long Handles:    2080
    Weak Short Handles:   59
    Dependent Handles:    22

从输出看,有一组数据特别刺眼,那就是: Async Pinned Handles = 8246 [System.Threading.OverlappedData],这是什么意思呢? 从英文名就能看出这是一个和 异步IO 相关的句柄,有些朋友应该知道,在异步IO的过程中,会有一个 byte[] 被 pinned 住,同时还有一个异步IO的上下文对象 OverlappedData

接下来的一个问题是:既然是异步IO,那它的 handle 是什么类型,如前面所说是 FileStream 还是 Socket ? 要想找出答案,就需要深挖 OverlappedData 对象,相关的命令是: !dumpheap -mt xxx & !do ... ,参考如下:

0:000> !DumpHeap /d -mt 00007ffccc3814a0
         Address               MT     Size
000001aa2acb39c8 00007ffccc3814a0       72
000001aa2acb3fd8 00007ffccc3814a0       72
000001aa2ad323d0 00007ffccc3814a0       72
...
0:000> !do 000001aa2acb39c8
Name:        System.Threading.OverlappedData
MethodTable: 00007ffccc3814a0
EEClass:     00007ffccc37ca18
Size:        72(0x48) bytes
File:        C:\xxx\xxx\vms_210819\System.Private.CoreLib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffccc21f508  40006b2        8  System.IAsyncResult  0 instance 0000000000000000 _asyncResult
00007ffccc110ae8  40006b3       10        System.Object  0 instance 000001aa2acb4020 _callback
00007ffccc381150  40006b4       18 ...eading.Overlapped  0 instance 000001aa2acb3980 _overlapped
00007ffccc110ae8  40006b5       20        System.Object  0 instance 000001aa2acb9fe8 _userObject
00007ffccc11f130  40006b6       28                  PTR  0 instance 000001aa2a9bd830 _pNativeOverlapped
00007ffccc11ecc0  40006b7       30        System.IntPtr  1 instance 0000000000000000 _eventHandle
0:000> !DumpObj /d 000001aa2acb3980
Name:        System.Threading.ThreadPoolBoundHandleOverlapped
MethodTable: 00007ffccc3812a0
EEClass:     00007ffccc37c9a0
Size:        72(0x48) bytes
File:        C:\xxx\xxx\vms_210819\System.Private.CoreLib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffccc3814a0  40006ba        8 ...ng.OverlappedData  0 instance 000001aa2acb39c8 _overlappedData
00007ffccc34fcd0  40006a4       10 ...ompletionCallback  0 instance 000001aa2acb3920 _userCallback
00007ffccc110ae8  40006a5       18        System.Object  0 instance 000001aa2acb38c8 _userState
00007ffccc380120  40006a6       20 ...locatedOverlapped  0 instance 000001aa2acb3960 _preAllocated
00007ffccc11f130  40006a7       30                  PTR  0 instance 000001aa2a9bd830 _nativeOverlapped
00007ffccc380eb8  40006a8       28 ...adPoolBoundHandle  0 instance 000001aa2acb3900 _boundHandle
00007ffccc1171c8  40006a9       38       System.Boolean  1 instance                0 _completed
00007ffccc34fcd0  40006a3      458 ...ompletionCallback  0   static 000001aa2acb4020 s_completionCallback
0:000> !DumpObj /d 000001aa2acb3900
Name:        System.Threading.ThreadPoolBoundHandle
MethodTable: 00007ffccc380eb8
EEClass:     00007ffccc37c870
Size:        32(0x20) bytes
File:        C:\xxx\xxx\vms_210819\System.Private.CoreLib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffccc1d76b0  40006a1        8 ...rvices.SafeHandle  0 instance 000001aa2acb1d30 _handle
00007ffccc1171c8  40006a2       10       System.Boolean  1 instance                0 _isDisposed

0:000> !DumpObj /d 000001aa2acb1d30
Name:        Microsoft.Win32.SafeHandles.SafeFileHandle
MethodTable: 00007ffccc3807c8
EEClass:     00007ffccc37c548
Size:        48(0x30) bytes
File:        C:\xxx\xxx\xxx\System.Private.CoreLib.dll
Fields:
              MT    Field   Offset                 Type VT     Attr            Value Name
00007ffccc11ecc0  4000bb4        8        System.IntPtr  1 instance 0000000000000428 handle
00007ffccc11b1e8  4000bb5       10         System.Int32  1 instance                4 _state
00007ffccc1171c8  4000bb6       14       System.Boolean  1 instance                1 _ownsHandle
00007ffccc1171c8  4000bb7       15       System.Boolean  1 instance                1 _fullyInitialized
00007ffccc2f1ae0  4001c3d       20 ...Private.CoreLib]]  1 instance 000001aa2acb1d50 _isAsync
00007ffccc380eb8  4001c3e       18 ...adPoolBoundHandle  0 instance 0000000000000000 <ThreadPoolBinding>k__BackingField

上面倒数第五行的 0000000000000428 就是具体的 handle 值,接下来就可以用 !handle 命令查看其值的具体信息。

0:000> !handle 0000000000000428 7
Handle 428
  Type         	File
  Attributes   	0
  GrantedAccess	0x100081:
         Synch
         Read/List,ReadAttr
  HandleCount  	2
  PointerCount 	65489

Type:File 可以看出,原来这 8000 多都是文件句柄哈。。。

写到这里貌似就到了死胡同了😪😪😪,虽然挖了一些信息,但这些信息还不足以让我找到问题根源,从引用链上来说,gchandles 中的这些对象是处于引用链的顶端,换句话说,我需要找到这条引用链下游的一些数据对象,一个好的入口点就是到 heap 中去挖。

3. 从托管堆找 OverlappedData 的徒孙辈

首先我们用 !dumpheap -stat 查看下托管堆。

0:000> !dumpheap -stat
Statistics:
              MT    Count    TotalSize Class Name
...

00007ffccc3c5e18   939360     52604160 System.Collections.Generic.SortedSet`1+Node[[System.Collections.Generic.KeyValuePair`2[[System.String, System.Private.CoreLib],[System.String, System.Private.CoreLib]], System.Private.CoreLib]]
00007ffccc1d2360    16492     69081162 System.Byte[]
000001aa2a99af00    10365     76689384      Free
00007ffccc1d1e18  1904987    116290870 System.String

既然是找引用链下游,那就从基础类型 System.String 或者 System.Byte[] 入手,这里我就选择前者,写了一个对 mt 下所有 address 进行分组统计的脚本,毕竟人肉是不可能的,从脚本的输出中我抽了几个 address 查看 !gcroot,大概都是类似这样的内容。

0:000> !gcroot 000001aa47a0c030
HandleTable:
    000001AA4469C090 (async pinned handle)
    -> 000001AA491EB908 System.Threading.OverlappedData
    -> 000001AA491EB8C0 System.Threading.ThreadPoolBoundHandleOverlapped
    -> 000001AA491EB860 System.Threading.IOCompletionCallback
    -> 000001AA491EAF30 System.IO.FileSystemWatcher
    -> 000001AA491EB458 System.IO.FileSystemEventHandler
    ...
    -> 000001AA47A0C030 System.String

0:000> !gcroot 000001aa2d3ea480
HandleTable:
    000001AA28FE9930 (async pinned handle)
    -> 000001AA2DD68220 System.Threading.OverlappedData
    -> 000001AA2DD681D8 System.Threading.ThreadPoolBoundHandleOverlapped
    -> 000001AA2DD68178 System.Threading.IOCompletionCallback
    -> 000001AA2DD67848 System.IO.FileSystemWatcher
    ...
    -> 000001AA2D3EA480 System.String

从整个引用链来看,里面都有一个 System.IO.FileSystemWatcher ,这和前面分析的 handle= File 是一致的,然后就是导出这些 string ,发现大部分都是和 appSettings 相关,如下所示:

string: appSettings:RabbitMQLogQueue
string: appSettings:MedicalMediaServerIP
string: appSettings:UseHttps
...

然后用 !strings 命令进行了模糊匹配,发现这样的string 高达 61w 。。。

到这里基本就能断定:appsettings 被 watch 了,但 watch 的方式有问题。。。

4. 寻找最终答案

将调查结果给了朋友之后,让朋友着重观察下对 appsetting 进行 watch 的方式是否有问题? 几个小时后,朋友终于找到了。


大概意思是说:本身已经通过设置 reloadOnChange=true 对 appsetings 进行了监控,但写码的人对这一块不熟悉,又通过每10s一次轮询对appsettings进行数据感知,问题就出现在这里。。。

三:总结

其实本次事故的主要原因还是在于对如何实时感知 appsettings 中最新数据的玩法不熟悉,一边用了 .netcore 自带的 reloadOnChange 监控,一边还用轮询的方式进行数据感知,所以说基础还是很重要的,不要想当然的去写! 😁😁😁

到此这篇关于.NET关于API 句柄泄漏分析的文章就介绍到这了,更多相关.NET API 句柄泄漏内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • .NET资源泄露与处理方案知识点分享

    .NET虽然拥有强大易用的垃圾回收机制,但并不是因为这样,你就可以对资源管理放任不管,其实在稍不注意的时候,可能就造成了资源泄露,甚至因此导致系统崩溃,到那时再来排查问题就已经是困难重重. 一.知识点简单介绍 常见的资源泄露有: 内存泄漏:非托管资源没有释放.非静态对象注册了静态实例. GDI泄露:字体. 句柄泄露:Socket或线程. 用户对象泄露:移除的对象未释放. 二.具体实例 1. 内存泄漏 很常见的现象是分不清哪些对象需要释放,对于控件.Stream等一些非托管资源也只管新增,却没有释

  • .NET关于API 句柄泄漏分析

    目录 一:背景 1. 讲故事 2. 什么是句柄 二: windbg 分析 1. 看问题表象 2. 查看句柄表 3. 从托管堆找 OverlappedData 的徒孙辈 4. 寻找最终答案 三:总结 一:背景 1. 讲故事 上上周有位朋友找到我,说他的程序CPU和句柄都在不断的增长,无回头趋势,查了好些天也没什么进展,特加wx寻求帮助,截图如下: 看的出来这位朋友也是非常郁闷,出问题还出两个,气人哈,关于 cpu 爆高的问题我准备单独用一篇文章去侦读,这篇就先聊聊 句柄泄漏 的问题,毕竟写了20多

  • VC中CWinThread类以及和createthread API的区别分析

    本文实例讲述了VC中CWinThread类以及和createthread API的区别分析,分享给大家供大家参考.具体分析如下: CWinThread CObject  └CCmdTarget     └CWinThread CWinThread对象代表在一个应用程序内运行的线程.运行的主线程通常由CWinApp的派生类提供:CWinApp由CWinThread派生.另外,CWinThread对象允许一给定的应用程序拥有多个线程. CWinThread支持两种线程类型:工作者线程(Worker

  • Yii2中Restful API原理实例分析

    本文实例分析了Yii2中Restful API原理.分享给大家供大家参考,具体如下: Yii2 有个很重要的特性是对 Restful API的默认支持, 通过短短的几个配置就可以实现简单的对现有Model的RESTful API 这里通过分析rest部分源码,简单剖析下yii2 实现 restful 的原理,并通过一些定制实现 对 关联模型的RESTful api 操作. ~ 代表 extends from 的关系 | | rest/ | | |-Action.php ~ `\yii\base\

  • .NET内存泄漏分析Windbg项目实例

    一:背景 讲故事 上个月有位朋友找到我,说他的程序出现了内存泄漏,不知道如何进一步分析,截图如下: 朋友这段话已经说的非常言简意赅了,那就上 windbg 说话吧. 二:Windbg 分析 1. 到底是哪一方面的泄漏 根据朋友描述,程序运行一段时间后,内存就炸了,应该没造成人员伤亡,不然也不会跟我wx聊天了,这里可以用 .time 看看当前的 process 跑了多久. 0:000> .time Debug session time: Thu Oct 21 14:54:39.000 2021 (

  • Erlang项目内存泄漏分析方法

    随着项目越来越依赖Erlang,碰到的问题也随之增加.前段时间线上系统碰到内存高消耗问题,记录一下troubleshooting的分析过程.线上系统用的是Erlang R16B02版本. 问题描述 有几台线上系统,运行一段时间,内存飙升.系统模型很简单,有网络连接,pool中找新的process进行处理.top命令观察,发现内存都被Erlang进程给吃完了,netstat命令查看网络连接数,才区区几K.问题应该是Erlang内存泄漏了. 分析方法 Erlang系统有个好处,可以直接进入线上系统,

  • PHP实现的同步推荐操作API接口案例分析

    本文实例分析了PHP实现的同步推荐操作API接口.分享给大家供大家参考,具体如下: 文档 1. 功能 同步推荐关系 2. 接口方法 syncRelation 3. 参数描述 OriginalUsername 查询的用户用户名 RecommandUsername 推荐人用户名 4. 返回值 status 1成功 9 失败 5. 备注 Status=1 原用户不存在,关系未建立 Status=2 推荐用户不存在,关系未建立 Status=3 原用户存在,并且没有推荐人,推荐用户也存在,成功建立关系

  • .NET某消防物联网后台服务内存泄漏分析

    目录 一:背景 1. 讲故事 二:Windbg 分析 1. 托管还是非托管 2. 到底是什么在泄漏 3. 无引用根为什么不被回收 4. 寻找创建 COM 组件的线程 三:总结 一:背景 1. 讲故事 去年十月份有位朋友从微信找到我,说他的程序内存要炸掉了...截图如下: 时间有点久,图片都被清理了,不过有点讽刺的是,自己的程序本身就是做监控的,结果自己出了问题,太尴尬了

  • C++ POSIX API超详细分析

    目录 1.网络通信 2.posix API 3.POSIX网络API 4.函数内部过程解析 4.1 socket套接字创建 4.2 bind 绑定端口 4.3 网络字节序和主机字节序 4.4 listen监听fd 4.5 connect发起连接请求 4.6 accept()接收请求建立连接 4.7 消息的发送和接收 4.8 粘包问题 4.9 close 1.网络通信 1.消息传递(管道.FIFO.消息队列) 2.同步(互斥量.条件变量.读写锁.文件和写记录锁.信号量) 3.共享内存(匿名的和具名

  • PHP对象递归引用造成内存泄漏分析

    通常来说,如果PHP对象存在递归引用,就会出现内存泄漏.这个Bug在PHP里已经存在很久很久了,先让我们来重现这个Bug,示例代码如下: <?php class Foo { function __construct() { $this->bar = new Bar($this); } } class Bar { function __construct($foo) { $this->foo = $foo; } } for ($i = 0; $i < 100; $i++) { $ob

  • ZooKeeper Java API编程实例分析

    本实例我们用的是java3.4.6版本,实例方便大家学习完后有不明白的可以在留言区讨论. 开发应用程序的ZooKeeper Java绑定主要由两个Java包组成: org.apache.zookeeper org.apache.zookeeper.data org.apache.zookeeper包由ZooKeeper监视的接口定义和ZooKeeper的各种回调处理程序组成. 它定义了ZooKeeper客户端类库的主要类以及许多ZooKeeper事件类型和状态的静态定义. org.apache.

随机推荐