1

我开始学习 GC 和 finalization,我遇到了一个非常简单的示例,其中应用程序的行为对我来说非常出乎意料。

(注意:我知道终结器只能用于非托管资源并使用一次性模式,我只是想了解这里发生了什么。)

这是一个简单的控制台应用程序,可生成“锯齿”模式的内存。内存上升到 90MB 左右,然后进行 GC,下降并再次开始上升,永远不会超过 90MB。

    class Program
    {
        static void Main(string[] args)
        {
            for (int i = 0; i < 100000; i++)
            {
                MemoryWaster mw = new MemoryWaster(i);
                Thread.Sleep(250);
            }
        }
    }

    public class MemoryWaster
    {
        long l = 0;
        long[] array = new long[1000000];

        public MemoryWaster(long l)
        {
            this.l = l;
        }

        //~MemoryWaster()
        //{
        //    Console.WriteLine("Finalizer called.");
        //}
    }

如果我用终结器删除注释,行为会非常不同——应用程序在开始时执行一到两次 GC,但随后内存以线性方式增加,直到它使用超过 1GB 的内存(此时我终止应用程序)

根据我的阅读,这是因为 GC 没有释放项目,而是将对象移动到终结队列。GC 启动一个线程来执行终结器方法,然后等待另一个 GC 移除终结对象。当终结器方法运行时间很长时,这可能是一个问题,但这里不是这种情况。

如果我每隔几次迭代手动触发一次运行 GC.Collect(),则应用程序会按预期运行,并且我会看到内存的锯齿模式被释放。

我的问题是 - 为什么应用程序使用的大量内存不会自动触发 GC?在包含终结器的示例中,GC 会在第一次之后再次运行吗?

4

1 回答 1

0

不要依赖终结器。它们是你永远不应该接触的安全网,而不是第一选择。如果终结者必须在你之后进行清理,那么你已经搞砸了。

我有两条关于一次性用品的基本规则,它们总是有效的:

  • 永远不要拆分实例的创建和处置。创建、使用、处置。全部在同一段代码中,理想情况下使用using 块
  • 如果你不能做第一件事——比如当你包装一些实现 IDisposeable 的东西时——你的类实现 IDisposeable 的唯一目的是中继 Dispose 调用。

至于GC:

当 GC 运行时,所有其他线程都必须暂停。这是一个绝对的规则。结果,GC 非常懒惰。它试图避免运行。事实上,如果它只在应用程序关闭期间运行一次,那是理想的情况。

于 2019-11-18T19:17:44.593 回答