收集Clear()vs new,GC影响

时间:2014-08-23 02:00:01

标签: .net garbage-collection

我在一个循环中创建了一堆MemoryStream并将它们添加到一个集合(在这种情况下是一个ArrayList)。
之后我迭代这个列表并处理这个流。 因为我遇到Outofmemoryexceptions,我决定定期处理列表,然后释放它。

然而,通过list = new ArrayList()执行此操作并没有改变内存消耗,无论是在监视它还是消除Outofmemoryexceptions时都没有。甚至调用GC.Collect()也没有改变它。我注意到只有在离开示波器后才释放内存。

然而,

调用List.Clear(),立即释放内存并且循环按预期工作。

那么,为什么会出现这种差异呢?这里的许多其他主题给人的印象是两个方法应该基本相同,list = new ArrayList()可能更有效,因为Clear()是一个O(n)操作。

我非常确定现有的内存流没有其他引用(我基本上做list.Add(new MemoryStream(...)

3 个答案:

答案 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;。