10

以下代码是我看到的问题的简化示例。由于字典太大,此应用程序在引发异常之前消耗了大约 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施加压力以释放内存?

然而,更令人困惑的是,如果我们等到任务完成,然后按回车键再次运行任务,内存就会被释放,那么显然之前的字典已经被垃圾回收了(不是吗?)。

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

任何解释或解决方案将不胜感激。

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

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

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

在示例中,字典填充到字典的限制,然后引发异常,它不会永远填充!这远远早于我们的内存已满,并且不会导致 OutOfMemoryException。因此结果是内存中有一个大对象,然后任务完成。

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

希望这能稍微澄清一下这个问题

4

6 回答 6

6

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

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

(2)你永远不会改变指向你的字典的指针,所以 GC 肯定没有理由去碰字典。

所以对我来说,你的程序正在做应该做的事情。

于 2012-07-03T17:15:33.590 回答
2

内存没有被释放,因为范围aMassiveList永远不会完成。当一个函数返回时,它会释放它内部创建的所有非引用资源。

在你的情况下,aMassiveList永远不要离开上下文。如果你希望你的函数永远不会返回,你必须找到一种方法来“处理”你的信息并发布它,而不是永远存储它们。

如果您创建的函数越来越多地分配资源并且从不释放它,您最终将消耗所有内存。

于 2012-07-03T17:15:10.570 回答
2

GC 只会释放未引用的对象,因此由于您的程序正在引用该字典,因此 GC 无法释放它

于 2012-07-03T17:18:15.957 回答
2

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

这一切都符合预期,尽管我们目前无法确定何时应该从 CLR 中释放此内存。

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

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

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

如果我理解正确,你是说你设置这个来填满内存。然后,在它崩溃之后(但在你点击返回开始一个新任务之前)你正在尝试这个程序之外的其他事情,比如在 Windows 中运行其他程序来尝试让 GC 收集内存,对吗?希望操作系统会与 GC 对话,并开始向它施压以完成它。

然而,更令人困惑的是,如果我们等到任务完成,然后按回车键再次运行任务,内存就会被释放,那么显然之前的字典已经被垃圾回收了(不是吗?)。

我想你回答了你自己的问题……在你点击返回开始新任务之前,它不一定被发布。新任务需要内存,所以它进入 GC,GC 愉快地从上一个任务中收集内存,现在已经结束(在从满内存中抛出之后)。

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

不知道能不能强制GC释放内存。一般来说,它会在需要的时候执行(尽管某些黑客类型可能知道一些巧妙的方法来强迫它的手。)当然,.NET 决定何时运行 GC,因为当程序只是坐在那里时没有发生任何事情,它很可能决定它不需要。至于操作系统是否可以迫使 GC 运行,从您的测试来看,答案似乎是“否”。可能有点反直觉。

那是你想要达到的目的吗?

于 2012-07-03T18:38:21.770 回答
0

您编写WasteMemory方法的方式,它永远不会退出(除非变量“i”溢出,今年不会发生)并且因为它永远不会退出,它将保留对内部字典的引用。

Daniel White 是对的,您应该了解 GC 的工作原理。

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

我看不出您希望 CLR/GC 在这里做什么。在您的WasteMemory 方法的一次运行中没有什么可以收集垃圾的。

然而,更令人困惑的是,如果我们等到任务完成,然后按回车键再次运行任务,内存就会被释放,那么显然之前的字典已经被垃圾回收了(不是吗?)。

当您按 Enter 时,将创建并启动一个新任务。这不是同一个任务,它是一个新任务- 一个新对象持有对新的 WasteOfMemory 实例的引用。

旧任务将继续运行,并且不会收集它使用的内存,因为旧任务一直在后台运行并且它一直在使用该内存。

我不确定为什么 - 最重要的是如何 - 你观察到旧任务的内存被释放。

于 2012-07-03T17:49:39.880 回答
-8

将您的方法更改为 using 语句

例子:

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
于 2012-07-03T17:16:49.313 回答