消费者线程垃圾收集问题

时间:2014-05-06 06:17:17

标签: .net multithreading garbage-collection

背景

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

消费者在等待冲动以争取工作:

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还会验证作业对象 root。但是,只要我们向系统提交另一个消费者线程的作业,就会释放内存并清理对象。但如果其他消费者接受了这项新工作,那么内存就不会被释放。

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

理论1.这项工作已经成为第1代或第2代,因此除非系统缺乏记忆,否则不会被收集。但是,一旦线程被唤醒并且开始执行另一项工作,因为内存远未达到低位,它就会收集它是没有意义的。

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

当系统忙于许多自动化工作时,消费者不会等待太长时间,因此问题不可见。但是在安静的日子里,只有用户手动提交一些大型工作的问题正在被提出,为什么服务处于空闲状态并具有如此高的内存使用率。因此,这不是一个关键问题,但它令人困惑。

任何指针?

由于

2 个答案:

答案 0 :(得分:0)

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

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

IModelJob job = _jobProvider.GetNextJob();

这两个陈述是错误的:
“内部范围,所以我希望这个工作超出每个循环的范围” “在这个阶段,这项工作超出了范围,不再被引用。” 在当前格式作业中,当您退出(而不是循环)while(_continueConsuming)时,该作业仅超出范围 以前的IModelJob作业只有在下次打到该行时才会超出范围 这正是你所看到的行为

将IModelJob作业放入使用块中 这样它就会超出范围IN循环

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?

答案 1 :(得分:0)

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

  

您的对象的内存不会被垃圾收集,因为它们超出了范围。

换句话说:如果你没有对某个对象的引用,意味着该对象已被收集。这意味着它符合条件进行收集,并且将在下次GC运行时收集。但是,你不能保证什么时候会这样。

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

在你的情况下(假设你的代码是正确编写的并且没有对相关对象进行任何引用),这是预期的行为:你的代码正在等待Pulse而不会进行任何分配,因此GC没有压力,也不太可能运行集合。当你开始下一份工作时,你开始分配内存,因此很快就会跟进GC,并收集上次工作中不再引用的内容。

所以我会说"理论2"是:GC正在完成预期的工作,并且没有什么可担心的。