记一次内存泄漏排查--windbg工具使用.md

问题跟踪

上线的一个系统,运行后客户反馈系统越来越慢,通过查看日志,得到反馈信息是sql链接超时Begin failed with SQL exception ---> System.InvalidOperationException: 超时时间已到。超时时间已到,但是尚未从池中获取连接。出现这种情况可能是因为所有池连接均在使用,并且达到了最大池大小
sql链接超时一般情况是sql链接没有释放造成的,重启sql服务即可。但是,此次重启并没有解决问题,于是重启iis的应用池,程序访问正常。
由此也产生了疑问,莫非是程序发生了内存泄漏,导致连接不能释放?
程序运行两三天,客户又反馈系统越来越慢,此时感觉查看进程,发现该应用占用内存由启动的200多M到现在的10个G以上,因此也猜测,既有可能是程序又了内存泄漏。
那么,如何查出是那里泄漏呢?日志没有中没有记录,系统日志也查不到有关信息。
此时,偶然看到一篇文章,介绍如何查高内存占用的问题,介绍使用 WinDbg 工具,那么我们就来试试这个工具。

解决问题

两篇文章如下:

  1. indbg分析高内存占用问题
  2. WinDbg知多少

下载 winsdksetup.exe
双击,选择Debugging Tools for Windows安装

关于如何收集内存信息请参考文章1,windbg 基本使用命令可参考文章2。

当程序占用内存再一次高大8G的时候,我使用[ProcDump](https://docs.microsoft.com/zh-cn/sysinternals/downloads/procdump) 来抓取 dump,接下来就是使用 windbg 分析 dump。

打开windbg

选择菜单File > Open Crash Dump,如下图

1

由于我们是net程序,所以需要进行如下操作,
选择菜单File > Symbol File Path,指定symbol search path 设置符号服务器与符号缓存SRV*D:\symbols*http://msdl.microsoft.com/download/symbols
见下图

image.png

加载 net4.0以上版本的模块.loadby sos clr,回车执行命令

分析问题

按照文章1的教程敲入命令:!dumpheap -stat

image.png

显示BUSY表示正在执行命令中,执行结果如下
image.png

显现 String 类型占用 1G左右,而 UnitOfWorkManager占用600M之多,
UnitOfWorkManager是管理一次数据库操作事务的工具类,此处占用这么多内存十分不合理,于是对000007fe9adaed38 16628281 665131240 xxxx.UnitOfWorkManager进行跟踪

执行命令
!dumpheap -mt 000007fe9adaed38 -min 200 查看 unitofworkmanager 超过 200k的

image.png

执行结果
image.png

结果中没有任何信息,表示unitofworkmanager 中没有超过200k的,那么我们再执行命令,!dumpheap -mt 000007fe9adaed38 去掉 200k的限制,会发现满屏都是40byte的 unitofwork
image.png

此时只能使用Ctrl+Break命令中止输出。
中止输出如下
image.png

占用分析

既然猜测是内存没释放,我们使用!gcroot命令来查看类型的引用对象

image.png

执行结果如下


image.png

陡然发现了Jobs.WindsorContainerJobActivator,莫不是因为这里使用不当,导致hangfire中的job不能正常使用?

赶紧查看此处代码

源代码分析

WindsorContainerJobActivator主要是为了hangfire中的job能使用windsor ioc

image.png
,hangfire通过ActivateJob来获取执行job的类。
现在怀疑是由于job使用ioc获取了类型,但是没有释放。
于是又参考了 abp hangfire模块中的做法,发现的确代码中缺少了内容,完整代码如下

/// 
    /// Hangfire windsor ioc
    /// 
    public class WindsorContainerJobActivator : JobActivator
    {
        /// 
        /// The _container.
        /// 
        private readonly IIocResolver _iocResolver;

        /// 
        /// Initializes a new instance of the  class.
        /// 
        /// 
        /// The container.
        /// 
        public WindsorContainerJobActivator(IIocResolver iocResolver)
        {
            this._iocResolver = iocResolver;
        }

        /// 
        /// The activate job.
        /// 
        /// 
        /// The type.
        /// 
        /// 
        /// The .
        /// 
        public override object ActivateJob(Type type)
        {
            return _iocResolver.Resolve(type);
        }

        public override JobActivatorScope BeginScope(JobActivatorContext context)
        {
            return new HangfireIocJobActivatorScope(this, _iocResolver);
        }

        class HangfireIocJobActivatorScope : JobActivatorScope
        {
            private readonly JobActivator _activator;
            private readonly IIocResolver _iocResolver;

            private readonly List _resolvedObjects;

            public HangfireIocJobActivatorScope(JobActivator activator, IIocResolver iocResolver)
            {
                _activator = activator;
                _iocResolver = iocResolver;
                _resolvedObjects = new List();
            }

            public override object Resolve(Type type)
            {
                var instance = _activator.ActivateJob(type);
                _resolvedObjects.Add(instance);
                return instance;
            }

            public override void DisposeScope()
            {
                _resolvedObjects.ForEach(_iocResolver.Release);
            }
        }
    }
 
 

重写JobActivatorBeginScope方法,提供ioc的scope即可

总结

解决这个问题大约用了两周时间,主要是通过各种日志和清空,分析不到具体的原因,只能瞎猜,期间还用了两天时间去查询sql连接池的使用情况,没有发现任何问题,不幸中的万幸是看到了windbg工具。
一边摸索windbg的使用方式,一边跟踪代码,最终也是找到问题并解决。
在解决问题过程中,基本掌握了windbg分析问题的步骤:

  1. 抓取dump文件
  2. 分析dump文件(windbg + 命令)
  3. 大胆猜想
  4. 结合源代码
  5. 验证

其中也暴露出了知识缺陷,多次接触ioc,并了解scope,但是在实际编码中会自觉的屏蔽掉相关知识点,造成此次问题。

阅读官方文档要仔细,官方文档中推荐的Hangfire.Windsor没有仔细阅读,事后发现和abp hangfire中的解决方案如出一辙。

你可能感兴趣的:(记一次内存泄漏排查--windbg工具使用.md)