【问题标题】:Consumer thread Garbage collection issue消费者线程垃圾回收问题
【发布时间】:2014-05-07 04:51:39
【问题描述】:

背景

我已经实现了支持多个消费者线程和多个生产者的消费者-生产者模式。

消费者等待一个脉搏来竞争工作:

private void DistributedConsume()
{

    while (_continueConsuming)
    {
        try
        {
            Monitor.Wait(_workerLockObject);
            IModelJob job = _jobProvider.GetNextJob();

            if (job != null)
            {
                //we found and dequeued job for processing
                if (job.JobType != JobType.TerminateWorker)
                {
                    job.PrioritizationStatus = PrioritizationStatus.Dequeued;

                    try
                    {
                        job.Execute();
                    }
                    catch (ThreadAbortException)
                    {
                        LoggerFacade.Log(LogCategory.Debug, "WorkerController.DistributedConsume(): Thread {0} has been aborted", Thread.CurrentThread.Name);
                        throw;
                    }
                    catch (Exception ex)
                    {
                        LoggerFacade.Log(LogCategory.Debug, "WorkerController.DistributedConsume(): Unhandled exception on thread {0}", Thread.CurrentThread.Name);
                    }
                }
                else
                {
                    _continueConsuming = false;
                }
            }
        }
        catch (ThreadAbortException)
        {
            LoggerFacade.Log(LogCategory.Debug, "WorkerController.DistributedConsume(): Thread {0} has been aborted", Thread.CurrentThread.Name);
            throw;
        }
        catch (Exception ex)
        {
            ExceptionHandler.ProcessException(ex, "Unexpected Exception occured attempting to fetch the next job");
        }
    }

    LoggerFacade.Log(LogCategory.OperationalInfo, "WorkerController.DistributedConsume(): Thread {0} is exiting", Thread.CurrentThread.Name);

}

正在执行的作业可能会消耗大量内存,但正如您所见,该作业是在内部范围内声明的,所以我希望该作业在每个循环中都超出范围,因此它及其所有引用的数据将有资格进行垃圾收集。

问题

作业执行,消耗大量内存,作业完成,消费者线程循环回 Monitor.Wait 并等待脉冲。到此阶段,该作业已超出范围,不再被引用。有问题的是,如果没有新作业排队并且线程没有脉冲,则内存使用率保持很高 - 无论我等待多长时间。使用 WinDbg,我还可以看到堆上的作业及其引用的所有后代对象。

这可能会让你认为我有内存泄漏,但 WinDbg 也会验证作业对象是 not 根的。但是,一旦我们向系统提交另一个作业,这个消费者线程拾取,内存就会被释放并清理对象。但如果其他消费者接手这项新工作,则似乎内存没有释放。

我无法理解这一点。我对可能发生的事情的偏执理论都不符合我对 G.C 工作原理的理解。

理论 1. 该作业已进入第 1 代或第 2 代,因此除非系统内存不足,否则不会被收集。但是,一旦线程被唤醒并开始执行另一个作业,它就会收集它是没有意义的,因为内存远非低。

理论 2。我没有第二个理论值得分享。

当系统忙于处理许多自动化作业时,消费者不会等待太久,因此问题不可见。但是在只有用户手动提交一些大型作业的安静日子里,人们提出了关于为什么服务因如此高的内存使用而闲置的问题。因此,这不是一个关键问题,但令人困惑。

任何指针?

谢谢

【问题讨论】:

    标签: .net multithreading garbage-collection


    【解决方案1】:

    使用 GC 以及监控任何托管应用程序的内存使用情况时要记住的关键是:

    您的对象的内存不会因为超出范围而被垃圾回收。

    换一种说法:如果您不再引用某个对象,那并不意味着该对象已被收集。这意味着它是有资格收集的,并且将在下次 GC 运行时收集。不过,你不能保证会是什么时候。

    在 .NET 应用程序中,GC 将作为内存分配过程的一部分自动运行:也就是说,如果您尝试创建一个新对象并且 Gen0 中没有空间容纳该对象, GC 将运行以释放所需的空间。如果您不分配,则不会发生收集。 (有一些罕见的例外;例如:最小化 WinForms 应用程序会 GC 并释放未引用的内存,尽管我可能从 .NET 2 开始就没有测试过这种行为。)

    在您的情况下(并假设您的代码编写正确并且不包含对相关对象的任何引用),因此这是预期的行为:您的代码正在等待 Pulse 并且不会进行任何分配,因此 GC 没有压力并且不太可能运行收集。当您开始下一个作业时,您开始分配内存,因此 GC 紧随其后并收集上一个作业不再引用的内容。

    所以我会说“理论 2”是:GC 完全按照预期进行,没有什么可担心的。

    【讨论】:

    • 当发布的代码清楚地包含对相关对象的引用时,为什么您认为代码不包含对相关对象的任何引用?该对象根本没有超出范围。看我的回答。
    • 我没有,但我是说假设是这种情况,这个问题仍然是基于一个无效的前提。即使考虑到您的回答,除非应用程序中其他地方的分配压力导致 GC,否则我不希望行为发生变化。
    • 但是,我会反驳说您的答案不正确。稍后会发表评论。
    • Blam - 在一段时间内连续 Monitor.Waits 和 try 意味着前一个作业变量超出范围,并且每次都重新声明。如果你是这个意思,它肯定在 try 中包含一个引用。
    • 丹 - 谢谢你的指点。我错误地认为 G.C 在系统内存受到压力之前不会运行,但是当 Gen 0 无法分配所需内存时将触发收集的观点提供了一些清晰性,并指出脉冲线程获得工作将是请求内存的工作。我会重新配置并确认。
    【解决方案2】:

    您是否使用任务管理器来测量内存使用情况?
    如果是这样,请使用像 ANTS 这样的真实记忆工具进行测试。
    任务管理器往往会夸大内存使用情况。

    工作超出范围?
    这肯定看起来像是对我的引用:

    IModelJob job = _jobProvider.GetNextJob();
    

    这两种说法都是错误的:
    “内部范围,所以我希望工作超出每个循环的范围”
    “到此阶段,该作业已超出范围,不再被引用。”
    在当前格式作业中,仅当您退出(而不是循环)while (_continueConsuming)
    之前的 IModelJob 作业只会在您下次点击该行时超出范围
    这正是您所看到的行为

    将 IModelJob 作业放入 using 块中
    这样它将超出循环范围

    using (IModelJob job = _jobProvider.GetNextJob()) 
    {
    
    }
    

    请尝试以下方法
    但不要将 GC 收集留在生产中。

    if (job != null)
    {
    ...
    }
    GC.Collect();
    
    
    using (IModelJob job = _jobProvider.GetNextJob()) 
    {
    
    }
    GC.Collect();
    

    旧链接:

    My object is not rooted, why wasn't it garbage collected?

    【讨论】:

    • 你也可以在处理块之后,调用垃圾收集器并礼貌地要求它清理,尽管它完全保留忽略你的请求并继续玩 pinochle 或它正在做的任何事情的权利不工作。
    • @MauriceReeves 是的。但我不认为强制收集是一个好习惯。
    • 我通常也会尽量避免它,但在这种情况下,这可能不是一个坏主意。
    • @MauriceReeves “当系统忙于处理许多自动化工作时,消费者不会坐等太久”。让它在需要时收集。多个消费者强制收集可能是不好的。
    • @MauriceReeves 一个小评论:在 Java 中,System.gc() 是垃圾收集运行时的提示。在 .NET 中,至少根据 MSDN,GC.Collect() 强制进行垃圾收集。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-06-25
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多