以下代码是我看到的问题的简化示例。由于字典过大,此应用程序在抛出异常之前会消耗大约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实例并开始新任务之前,内存不会被释放。
希望这会澄清一下这个问题
答案 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