【问题标题】:Why does the c# garbage collector not keep trying to free memory until a request can be satisfied?为什么 c# 垃圾收集器在满足请求之前不会继续尝试释放内存?
【发布时间】:2013-01-21 08:35:54
【问题描述】:

考虑下面的代码:

using System;

namespace memoryEater
{
    internal class Program
    {
        private static void Main(string[] args)
        {
            Console.WriteLine("alloc 1");
            var big1 = new BigObject();

            Console.WriteLine("alloc 2");
            var big2 = new BigObject();

            Console.WriteLine("null 1");
            big1 = null;

            //GC.Collect();

            Console.WriteLine("alloc3");
            big1 = new BigObject();

            Console.WriteLine("done");
            Console.Read();
        }
    }

    public class BigObject
    {
        private const uint OneMeg = 1024 * 1024;
        private static int _idCnt;
        private readonly int _myId;
        private byte[][] _bigArray;

        public BigObject()
        {
            _myId = _idCnt++;
            Console.WriteLine("BigObject {0} creating... ", _myId);

            _bigArray = new byte[700][];

            for (int i = 0; i < 700; i++)
            {
                _bigArray[i] = new byte[OneMeg];
            }

            for (int j = 0; j < 700; j++)
            {
                for (int i = 0; i < OneMeg; i++)
                {
                    _bigArray[j][i] = (byte)i;
                }
            }
            Console.WriteLine("done");
        }

        ~BigObject()
        {
            Console.WriteLine("BigObject {0} finalised", _myId);
        }
    }
}

我有一个类 BigObject,它在其构造函数中创建一个 700MiB 数组,并且有一个 finalize 方法,除了打印到控制台之外什么都不做。在 Main 中,我创建了两个这样的对象,释放一个,然后创建第三个。

如果这是为 32 位编译的(以便将内存限制为 2 gigs),则在创建第三个 BigObject 时会抛出内存不足异常。这是因为当第三次请求内存时,无法满足请求,因此垃圾收集器运行。但是,第一个准备好被收集的 BigObject 有一个终结器方法,因此不是被收集,而是被放置在终结队列中并被终结。然后垃圾收集器停止并抛出异常。但是,如果取消注释对 GC.Collect 的调用,或者删除 finalize 方法,代码将运行良好。

我的问题是,为什么垃圾收集器没有尽其所能满足内存请求?如果它运行两次(一次完成,一次释放),上面的代码就可以正常工作。垃圾收集器不应该继续完成并收集,直到在抛出异常之前无法释放更多内存,有没有办法将其配置为以这种方式运行(在代码中或通过 Visual Studio)?

【问题讨论】:

  • 问题是由终结器中的副作用引起的。不要那样做!
  • 几年前我也遇到过这个问题并写了一篇博文。见:xacc.wordpress.com/2011/02/22/gc-suppressfinalize(也见 cmets)
  • 有趣,但如果 Console.WriteLine 从终结器中删除,代码仍然会失败。

标签: c# garbage-collection finalizer


【解决方案1】:

GC 何时工作并尝试回收内存是不确定的。

如果您在 big1 = null 之后添加此行。但是,您应该小心强制 GC 收集。除非您知道自己在做什么,否则不建议这样做。

GC.Collect();
GC.WaitForPendingFinalizers();

Best Practice for Forcing Garbage Collection in C#

When should I use GC.SuppressFinalize()?

Garbage collection in .NET (generations)

【讨论】:

  • 我知道这是不好的做法,它只是一个演示应用程序来演示问题。问题是,为什么垃圾收集器在抛出异常之前不运行终结器和释放内存(这将允许进行分配)?
  • @SeanReid stackoverflow.com/questions/10016541/… 这个。现在应该很明显为什么 GC 从未自动发生。您只是在 LOH 上创建对象(请记住,int 类型,您使用它们的方式,是在堆栈上分配的,不必收集)。您永远不会填满第 0 代,因此永远不会发生 GC。
  • 问题是,GC 确实运行了,并且释放对象的终结器也运行。 (注意应用程序是 32 位的,以确保我们在 2Gb 时耗尽内存,而不是开始分页内存)。问题是 GC 需要运行两次,一次运行终结器,另一次实际释放内存。
  • GC 需要运行两次是没有析构函数(终结器)的主要原因之一。
【解决方案2】:

我猜是因为垃圾回收期间终结器执行的时间是未定义的。不能保证在任何特定时间释放资源(除非调用 Close 方法或 Dispose 方法。),终结器的运行顺序也是随机的,因此您可以让另一个对象上的终结器等待,而您的对象等待那个.

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-11-22
    • 1970-01-01
    • 2019-05-15
    • 1970-01-01
    • 2010-10-27
    • 2013-12-14
    • 1970-01-01
    相关资源
    最近更新 更多