【问题标题】:System.Threading.Timer windows service memory leakSystem.Threading.Timer windows服务内存泄漏
【发布时间】:2018-05-31 22:15:52
【问题描述】:

我有一个作为 Windows 服务运行的 c# 控制台应用程序。应用程序的主线程使用System.Threading.Timer 来管理数据库的“扫描”。当扫描在计时器滴答时开始时,计时器会创建一个对象以在数据库中查找工作,并且当发现工作时该对象会引发一个事件以将工作派生到另一个线程中。

我似乎遇到了内存泄漏,我有限的windbg 查询内存转储的能力让我认为计时器没有向 GC 释放某些内容。

windbg:

!dumpheap -type System.Object[]
...
02b01ae0 73a20cbc    32656   
02b09a80 73a20cbc     4112     
02b0e8e0 73a20cbc    16336     
02b5bf88 73a20cbc     1040     
02b5c3a8 73a20cbc     2064
10ee1010 73a20cbc 268435472     

Statistics:
      MT    Count    TotalSize Class Name
04210964        1           32 System.Func`2[[System.Type, mscorlib],[System.Func`2[[System.Object[], mscorlib],[Newtonsoft.Json.JsonConverter, Newtonsoft.Json]], mscorlib]]
7366a6a4        1           48 System.Collections.Generic.Dictionary`2[[System.String, mscorlib],[System.Object[], mscorlib]]
04210a5c        1           48 System.Collections.Generic.Dictionary`2[[System.Type, mscorlib],[System.Func`2[[System.Object[], mscorlib],[Newtonsoft.Json.JsonConverter, Newtonsoft.Json]], mscorlib]]
73a20cbc     1194    268556916 System.Object[]
Total 1198 objects
0:000> !gcroot 10ee1010 
Thread 2dfc:
    04f1f70c 72db0687 System.Net.TimerThread.ThreadProc()
        ebp+50: 04f1f710 (interior)
            ->  02ac33a8 System.Object[]
            ->  072a5a38 System.Net.ServerCertValidationCallback
            ->  072a5a18 System.Net.Security.RemoteCertificateValidationCallback
            ->  10ee1010 System.Object[] 

简化和浓缩的代码:

TimeSpan timerInterval = TimeSpan.FromSeconds(5);
static Timer t;
public void startTimer()
{
    t = new System.Threading.Timer(TimerTick, null, TimeSpan.Zero, timerInterval);
}
public void TimerTick(Object TimerState)
{
    //run each query sweep synchronously
    t.Change(Timeout.Infinite, Timeout.Infinite);
    List<Query> queries = GetQueries();
    foreach (var query in queries)
    {
        var search = new QueryProcessor(query);
        //short term publisher?
        search.resultFoundEvent += QueryResultEventListener;
        search.RunSearch();
    }
    t.Change(timerInterval, timerInterval);
}
public void RunJob(JobDetails job)
{
    Task.Factory.StartNew(() => job.Execute(JobCallback));
}
//long term subscriber
public void QueryResultEventListener(object sender, FakeEventArgs e)
{
    RunJob(e.jobdetails);
}
public void JobCallback(JobDetails jobsuccess)
{
    //job was completed
}

如上所述,我不认为这是一个传统的事件处理程序泄漏,而上面的windbg!dumpheap -stat 的底部只有一个巨大的条目,用于System.Object[]

【问题讨论】:

  • 您在转储前是否进行了GC.collectWaitForPendingFinalizers
  • @TheGeneral 这个windbg 是基于在客户端机器上运行的应用程序的任务管理器的内存转储。上面的代码是生产中运行的代码的简化版本,但都没有显式调用GC
  • 问题是,很难判断这只是等待清除的垃圾还是泄漏。我以前从未使用过windbg,在担心泄漏之前它的相关信息
  • 您可以使用 JetBrains dotMemory 以更方便的方式分析内存转储(可能其他内存分析器也可以,不知道)。
  • @Ed.ward 感谢您的建议,这个工具在帮助我方面绝对是优越的

标签: c# .net memory-leaks timer windbg


【解决方案1】:

使用 jetBrains dotMemory,我能够确定打开的句柄来自于从 job.Execute() 中的堆栈向下引用的托管代码调用的代码行。它正在全局设置一个委托并导致ServerCertificateValidationCallback 固定一个句柄并且永远不会释放。

ServicePointManager.ServerCertificateValidationCallback += (sender, certificate, chain, sslPolicyErrors) => { }; 感觉它以“短期发布者,长期订阅者”的经典内存泄漏方式表现,因为调用此行的构造函数的父对象在不久之后被释放。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2017-08-30
    • 2014-04-19
    • 2011-02-18
    • 1970-01-01
    • 2018-01-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多