任务完成后C#不释放内存

时间:2012-07-03 17:01:22

标签: c# .net memory-management garbage-collection

以下代码是我看到的问题的简化示例。由于字典过大,此应用程序在抛出异常之前会消耗大约4GB的内存。

 class Program
 {
    static void Main(string[] args)
    {
        Program program = new Program();

        while(true)
        {
            program.Method();
            Console.ReadLine();
        }
    }

    public void Method()
    {
        WasteOfMemory memory = new WasteOfMemory();
        Task tast = new Task(memory.WasteMemory);
        tast.Start();
    }


}

public class WasteOfMemory
{
     public void WasteMemory()
     {
         Dictionary<string, string> aMassiveList = new Dictionary<string, string>();

         try
         {
             long i = 0;
             while (true)
             {
                 aMassiveList.Add(i.ToString(), "I am a line of text designed to waste space.... I am exceptionally useful........");
                 i++;
             }

         }
         catch(Exception e)
         {
             Console.WriteLine("I have broken myself");
         }
     }
}

这一切都符合预期,尽管我们目前无法解决的问题是何时应从CLR中释放此内存。

我们让任务完成,然后模拟了内存过载情况,但字典消耗的内存未被释放。由于操作系统内存不足,是不是要对CLR施加压力才能释放内存?

然而更令人困惑的是,如果我们等到任务完成,然后按Enter键再次运行任务内存被释放,所以很明显以前的字典已经被垃圾收集了(不是吗?)。

那么,为什么内存没有被释放?我们怎样才能让CLR释放内存?

非常感谢任何解释或解决方案。

编辑:在回复之后,尤其是贝斯卡,很明显我对这个问题的描述并不是最清楚的,所以我会尽力澄清。

代码可能不是最好的例子,对不起!尝试复制问题是一段快速的原始代码。

这里使用字典来复制我们有一个大型自定义数据对象的事实,它填充了我们大部分内存,然后在任务完成后就不会释放。

在这个例子中,字典填满字典的限制然后抛出一个异常,它不会永远填充!这在我们的内存已满之前就已存在,并且它不会导致OutOfMemoryException。因此结果是内存中的大对象,然后任务完成。

此时我们希望字典超出范围,因为任务和方法'方法'都已完成。因此,我们希望字典被垃圾收集并回收内存。实际上,在再次调用“Method”,创建新的WasteOfMemory实例并开始新任务之前,内存不会被释放。

希望这会澄清一下这个问题

6 个答案:

答案 0 :(得分:5)

垃圾收集器只释放内存中不再使用的位置,这些位置没有指向它们的指针。

(1)您的程序无限运行而不会终止

(2)你永远不会改变指向你字典的指针,所以GC当然没有理由触及字典。

所以对我来说,你的程序正在完全它应该做什么。

答案 1 :(得分:2)

由于范围aMassiveList永远不会完成,因此未释放内存。当函数返回时,它会释放在其中创建的所有未引用的资源。

在您的情况下,aMassiveList永远不会离开上下文。如果您希望您的功能永远不会返回,您必须找到一种方法来“处理”您的信息并将其释放,而不是永久存储它们。

如果您创建的函数越来越多地分配资源并且永远不会释放它,那么您最终会占用所有内存。

答案 2 :(得分:2)

GC只会释放未引用的对象,因此当程序引用字典时,GC无法释放它

答案 3 :(得分:1)

好的,我一直在关注这个...我认为有一些问题,其中一些人已经触及,但我认为没有回答真正的问题(诚然,我花了一些时间才认识到,而且我不确定我现在都在回答你想要的东西。)

  

这一切都符合预期,尽管我们目前无法解决的问题是何时应从CLR中释放此内存。

正如其他人所说,当任务正在运行时,字典将不会被释放。它被使用了。它会变得更大,直到你的内存耗尽。我很确定你明白这一点。

  

我们让任务完成,然后模拟了内存过载情况,但字典消耗的内存未被释放。由于操作系统内存不足,是不是要对CLR施加压力才能释放内存?

我认为,这是真正的问题。

如果我理解正确,你就是说你设置它来填补内存。然后,在它崩溃之后(但是在你返回以开始新任务之前)你正在尝试此程序之外的其他事情,例如在Windows中运行其他程序以尝试将GC运行到收集记忆,对吗?希望操作系统能够与GC通信,并开始向它施加压力以实现它的目的。

  

然而更令人困惑的是,如果我们等到任务完成,然后按Enter键再次运行任务内存被释放,所以很明显以前的字典已经被垃圾收集了(不是吗?)。

我认为你回答了自己的问题......在你回到开始一项新任务之前,它并没有被释放。新任务需要内存,因此它进入GC,GC很乐意收集上一个任务的内存,该任务现在已经结束(从完全内存中抛出)。

  

那么,为什么内存没有被释放?我们怎样才能让CLR释放内存?

我不知道你可以强制GC释放内存。一般来说,它会在它需要时执行它(虽然一些黑客类型可能知道一些光滑的方式来强制它的手。)当然,.NET决定何时运行GC,并且因为当程序只是坐在那里时没有发生任何事情,它很可能决定它不需要。至于操作系统是否可以对GC进行压力运行,从测试中可以看出答案是“不”。有点反直觉。

那是你想要的吗?

答案 4 :(得分:0)

你编写WasteMemory方法的方式,它永远不会退出(除非变量“i”溢出,今年不会发生)和因为它永远不会退出它会保持 IN USE 对内部词典的引用。

丹尼尔怀特是对的,你应该读一下GC是如何运作的。

如果正在使用引用,GC将不会收集引用的内存。 否则,任何程序将如何运作?

我没看到你期望CLR / GC在这里做什么。在您的WasteMemory方法的一次运行中没有任何垃圾收集。

  

然而更令人困惑的是,如果我们等到任务完成,然后按Enter键再次运行任务内存被释放,所以很明显以前的字典已经被垃圾收集了(不是吗?)。

按Enter键时,将创建并启动新任务。它不是相同的任务,它是新任务 - 一个新对象,包含对新WasteOfMemory实例的引用。

旧任务将继续运行,并且它使用的内存将 NOT 被收集,因为旧任务在后台继续运行并且它保持 USING 该内存。

我不确定为什么 - 最重要的是如何 - 你会看到正在发布的旧任务的记忆。

答案 5 :(得分:-8)

将您的方法更改为使用声明

示例:

Using (WateOfMemory memory = new WateOfMemory())
{
    Task tast = new Task(memory.WasteMemory); 
    tast.Start();
 }

并添加可处置的WateOfMemoryClass(顺便提一下你的构造函数是WasteOfMemory)

#region Dispose
    private IntPtr handle;
    private Component component = new Component();
    private bool disposed = false;

    public WateOfMemory()
    {

    }

    public WateOfMemory(IntPtr handle)
    {
        this.handle = handle;
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    private void Dispose(bool disposing)
    {
        if(!this.disposed)
        {
            if(disposing)
            {
            component.Dispose();
            }

            CloseHandle(handle);
            handle = IntPtr.Zero;            
        }
        disposed = true;         
    }

    [System.Runtime.InteropServices.DllImport("Kernel32")]
    private extern static Boolean CloseHandle(IntPtr handle);

    ~WateOfMemory()      
    {
        Dispose(false);
    }
    #endregion