我在一个循环中创建了一堆MemoryStream并将它们添加到一个集合(在这种情况下是一个ArrayList)。
之后我迭代这个列表并处理这个流。
因为我遇到Outofmemoryexceptions,我决定定期处理列表,然后释放它。
list = new ArrayList()
执行此操作并没有改变内存消耗,无论是在监视它还是消除Outofmemoryexceptions时都没有。甚至调用GC.Collect()也没有改变它。我注意到只有在离开示波器后才释放内存。
然而,调用List.Clear()
,立即释放内存并且循环按预期工作。
list = new ArrayList()
可能更有效,因为Clear()是一个O(n)操作。
我非常确定现有的内存流没有其他引用(我基本上做list.Add(new MemoryStream(...)
)
答案 0 :(得分:3)
嗯,是的区别。 ArrayList.Clear()将所有元素设置为null。这使得这些元素立即有资格收集。
如果重新分配ArrayList,那么收集原始ArrayList的确切时间就很重要。只有然后才会收集元素。如果原始ArrayList很大(超过7083个项目),那么它的底层数组将最终出现在Large Object Heap中。哪些不经常收集。所以这些元素也会存在一段时间。增加OOM的几率。
你应该看看这里的大图,你的节目在仍然能够完成其工作的边缘摇摇欲坠。随着时间的推移,这很少会好转您需要认真考虑进行大幅度的重写,例如,将虚拟机的使用量减半,这样您就可以暂时休息一下。或者采取极其简单的解决方案。翻转交换机并定位到64位操作系统。今天广泛使用。
答案 1 :(得分:1)
在调试模式下,JIT将所有局部变量(物理上:堆栈位置和保持引用的寄存器)的生命周期延长到函数末尾。您可以看到随机对象生存期扩展。
此行为不违反GC保证。从不删除任何内容的GC是有效的GC,尽管不是很有用。
明确地将变量清空为null并将因子分解出来可以在这里提供帮助。
使用list = new ArrayList()
覆盖引用变量时,可能仍有其他对象引用旧列表。它们可能在您的代码中的某处显式,或者只是偶然的局部变量,它们仍然保留旧的引用但未被使用。
闭包也容易捕获引用。
答案 2 :(得分:0)
在@ user2864740的评论之后,我写了一个小测试例程,他们说得对:效果只出现在调试模式。此外,仅当i new
循环的 end 处的列表时,而不是将相同的语句移动到开头时:
static void Main(string[] args)
{
using (StreamWriter w = new StreamWriter(@"d:\tststream.123", false, Encoding.Default))
for (int i = 0; i < (1 << 20); i++)
w.WriteLine(Guid.NewGuid());
List<MemoryStream> list = new List<MemoryStream>();
for (int j = 0; j < 100; j++)
{
for (int i = 0; i < 30; i++)
{
list.Add(new MemoryStream(File.ReadAllBytes(@"d:\tststream.123")));
}
list = new List<MemoryStream>();
Console.WriteLine(j.ToString());
}
}
在第二次迭代时,在调试模式下编译时会抛出outofmemory(当然是32位)。在发布中编译它或将list = new List<MemoryStream>();
移动到循环的开头,它会继续&#34;无限期地#34;。