垃圾收集器测试

4

我编写了一个简单的.NET表单应用程序来测试.NET如何处理内存以及垃圾回收器如何进行清理。

表单应用程序的GUI如下:

Application GUI

代码如下:

public partial class Form1 : Form
{
    private readonly IList<byte[]> _btyList = new List<byte[]>();

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        int i = 0;

        while (i < 3)
        {
            byte[] buffer = File.ReadAllBytes(@"C:\PFiles\20131018_nl_metro holland.pdf");
            _btyList.Add(buffer);
            i++;
        }
    }

    private void button2_Click(object sender, EventArgs e)
    {
        int i = 0;

        while (i < _btyList.Count)
        {
            _btyList[i] = null;
            i++;
        }
    }

    private void button3_Click(object sender, EventArgs e)
    {
        GC.Collect();
    }
}

当我添加一些字节数组到私有的字节数组列表中时,这当然会对应用程序的内存使用产生影响: 添加字节数组后的内存使用情况 现在,当我按下清除内存按钮时,内存使用量将保持不变。我可以等待几个小时,但它不会改变。如果我在清除内存后按下垃圾回收按钮,它就会立即释放内存: 垃圾回收后的内存使用情况 问题是:为什么在这种情况下垃圾回收器不起作用?

5
“它需要运行”——谁说的? 如果您有4GB内存,360MB可能低于收集阈值。仅仅因为您认为它以某种特定的方式工作并不意味着这是正确的。通常情况下,您不应该担心或考虑垃圾回收何时运行,除非您正在编写关键代码。 - Mitch Wheat
2
不用担心,垃圾回收器已经由专家测试过了。 - H H
1
@HenkHolterman:当然,但如果你使用它,你必须知道它是如何确切地工作的... - Kees de Wit
1
很少有人确切地知道它是如何工作的。最重要的事情是要知道:永远不要调用 GC.Collect() - H H
1
不,你要先谷歌搜索“fReachable”,并阅读你能找到的所有内容。 - H H
显示剩余6条评论
4个回答

4
垃圾回收器没有运行,因为它不需要。如果内存不低,则无需进行回收。
如果您有4GB的内存,360MB可能低于收集阈值。
通常情况下,除非您编写时间或内存关键代码,否则不应担心或考虑GC何时运行。
参考:垃圾回收基础.NET中的垃圾回收理解
垃圾回收是在以下情况之一发生的:
  • 系统物理内存较低。

  • 托管堆上分配对象使用的内存超过了可接受的阈值。此阈值随进程运行而不断调整。

  • 调用了GC.Collect方法。在几乎所有情况下,您不需要调用此方法,因为垃圾回收器会持续运行。此方法主要用于特殊情况和测试。


3
垃圾回收相对较慢,CLR并不会在每次内存空闲时都进行回收,仅在需要时才进行。因此,“释放”数组并不会触发它。
你的实验完全成功了:你学到了一些关于垃圾回收如何工作的知识。

那就是它的确切想法。 - Kees de Wit
如果我将1GB添加到内存中并清除内存,然后重复此操作几次,它将抛出OutOfMemory异常。如何解释这个问题?垃圾收集器仍然没有起作用吗? - Kees de Wit
1
@KeesdeWit - 了解一下分段和大对象堆。 - H H
1
@MitchWheat,使用“慢”这样的主观术语就存在问题。一切都是相对的。然而,每次将 null 赋值时运行 GC 在许多情况下都会产生不利影响。或者我误解了它的潜在影响? - David Arno
1
我已经通过将“慢速”更改为“相对慢速”来“澄清”我的答案。在我看来,这是一个愚蠢的改变,因为慢是一种固有的相对术语,所以我创建了一个重言。不过,这可能会让那些坚称我的答案在没有它的情况下是错误的人感到更加愉快。 - David Arno
显示剩余4条评论

2

它的行为是正确的。垃圾收集器运行是不确定的,你无法确定它何时运行。

当GC感到需要释放之前分配的内存以满足新的内存分配需求时,它将收集内存。

来自MSDN -

每次创建一个新对象时,公共语言运行时都会从托管堆中为该对象分配内存。只要托管堆中有地址空间可用,运行时就会继续为新对象分配空间。然而,内存并非无限制的。最终,垃圾收集器必须执行收集以释放一些内存。垃圾收集器的优化引擎根据正在进行的分配确定执行收集的最佳时间。当垃圾收集器执行收集时,它检查托管堆中不再被应用程序使用的对象,并执行必要的操作以回收其内存。


2

垃圾回收器在发生触发事件时运行。触发事件可能是分配失败或Windows生成的低内存事件。

将变量设置为null不会以任何方式进行跟踪,也不是触发事件。

您的情况是使用GC.Collect有效情况,因为您了解垃圾回收器没有的释放模式知识。


即使如此,只有在遇到问题时才应执行此操作。运行GC.Collect还会清除GC统计信息,并可能导致以后的性能损失。 - Eli Algranti
@EliAlgranti 说得有道理。另一方面,CLR 永远不会自动返回内存(除非操作系统触发低内存事件,这时已经太晚了——例如文件缓存已经被清除)。我不希望桌面应用程序对我的系统做出这样的事情。 - usr

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接