为什么不反对与终结得到收集,即使是无根?即使是

2023-09-04 08:57:06 作者:愛 妳就是第壹眼的事

我用不得到由 GC 如果的Dispose()收集终结对象时遇到的问题没有明确要求。 我知道,我应该叫的Dispose()明确,如果一个对象实现的IDisposable ,但我一直认为它是安全的依靠框架和当一个物体变得未引用它可以被回收。

I've encountered an issue with finalizable objects that doesn't get collected by GC if Dispose() wasn't called explicitly. I know that I should call Dispose() explicitly if an object implements IDisposable, but I always thought that it is safe to rely upon framework and when an object becomes unreferenced it can be collected.

但有些实验的WinDbg / SOS / sosex我发现后,如果 GC.Sup pressFinalize()没有呼吁终结对象也没有得到收集,甚至如果它变成无根。所以,如果你大量使用终结对象(的DbConnection文件流,等等),并没有明确的处置他们,你可能会遇到过高的内存占用,甚至 OutOfMemoryException异常

But after some experiments with windbg/sos/sosex I've found that if GC.SuppressFinalize() wasn't called for finalizable object it doesn't get collected, even if it becomes unrooted. So, if you extensively use finalizable objects(DbConnection, FileStream, etc) and not disposing them explicitly you can encounter too high memory consumption or even OutOfMemoryException.

下面是一个示例应用程序:

Here is a sample application:

public class MemoryTest
{
    private HundredMegabyte hundred;

    public void Run()
    {
        Console.WriteLine("ready to attach");
        for (var i = 0; i < 100; i++)
        {
            Console.WriteLine("iteration #{0}", i + 1);
            hundred = new HundredMegabyte();
            Console.WriteLine("{0} object was initialized", hundred);
            Console.ReadKey();
            //hundred.Dispose();
            hundred = null;
        }
    }

    static void Main()
    {
        var test = new MemoryTest();
        test.Run();
    }
}

public class HundredMegabyte : IDisposable
{
    private readonly Megabyte[] megabytes = new Megabyte[100];

    public HundredMegabyte()
    {
        for (var i = 0; i < megabytes.Length; i++)
        {
            megabytes[i] = new Megabyte();
        }
    }

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    ~HundredMegabyte()
    {
        Dispose(false);
    }

    private void Dispose(bool disposing)
    {
    }

    public override string ToString()
    {
        return String.Format("{0}MB", megabytes.Length);
    }
}

public class Megabyte
{
    private readonly Kilobyte[] kilobytes = new Kilobyte[1024];

    public Megabyte()
    {
        for (var i = 0; i < kilobytes.Length; i++)
        {
            kilobytes[i] = new Kilobyte();
        }
    }
}

public class Kilobyte
{
    private byte[] bytes = new byte[1024];
}

即使经过10次迭代,你可以发现,内存消耗太高(从700MB到1GB),甚至更高的获取与更多的迭代。附加到进程与WinDBG中后,您可以发现所有大对象是无根的,但没有收集。

Even after 10 iterations you can find that memory consumption is too high(from 700MB to 1GB) and gets even higher with more iterations. After attaching to process with WinDBG you can find that all large objects are unrooted, but not collected.

如果你调用形势的变化燮pressFinalize()明确:即使在高pressure内存消耗大约是300-400MBs的稳定和WinDBG中表明,没有无根的对象,内存是免费的。

Situation changes if you call SuppressFinalize() explicitly: memory consumption is stable around 300-400MB even under high pressure and WinDBG shows that there are no unrooted objects, memory is free.

所以,问题是:它是在框架中的错误?有没有合理的解释?

So the question is: Is it a bug in framework? Is there any logical explanation?

详细信息:

每次迭代后,windbg中表明:

After each iteration, windbg shows that:

在结束队列为空 freachable队列为空 第2代包含previous迭代对象(百) 从previous迭代对象是无根

推荐答案

有一个终结不行为相同的方式作为对象缺少一项的对象。

An object with a finalizer doesn't behave the same way as an object lacking one.

当发生GC和副pressFinalize没有被调用,GC将无法收集的实例,因为它必须执行终结。因此,终结执行,对象实例提升到第1代(对象,它躲过了第一GC),即使它已经没有任何生存的参考。

When a GC occurs, and SuppressFinalize has not been called, the GC won't be able to collect the instance, because it must execute the Finalizer. Therefore, the finalizer is executed, AND the object instance is promoted to generation 1 (object which survived a first GC), even if it's already without any living reference.

第1代(和Gen2)对象被认为是长寿命,并且将被视为垃圾收集仅在第一代GC不足以释放足够的内存。我认为,在测试期间,第一代GC始终是足够了。

Generation 1 (and Gen2) objects are considered long lived, and will be considered for garbage collection only if a Gen1 GC isn't sufficient to free enough memory. I think that during your test, Gen1 GC is always sufficient.

该行为对GC性能产生影响,因为它否定了通过让几代(你在第一代时间短的对象)所带来的最优化。

This behavior has an impact on GC performance, as it negates the optimisation brought by having several générations (you have short duration objects in the gen1 ).

从本质上讲,有一个终结,并从调用它会一直推动已经死亡对象的长寿命堆,这是不是一件好事未能prevent的GC。

Essentially, having a Finalizer and failing to prevent the GC from calling it will always promote already dead objects to the long lived heap, which isn't a good thing.

您应该因此妥善处置你的IDisposable的对象,避免终结,如果没有必要的(如果有必要,实现IDisposable并调用GC.Sup pressFinalize。)

You should therefore Dispose properly your IDisposable objects AND avoid Finalizers if not necessary (and if necessary, implement IDisposable and call GC.SuppressFinalize. )

编辑: 我没看过code例子不够好:你的数据看起来就像是指居住在大对象堆(LOH),但实际上并非如此:你有很多包含引用的小型阵列字节的树小数组末端

I didn't read the code example well enough: Your data looks like it is meant to reside in the Large Object Heap (LOH), but in fact isn't: You have a lot of small arrays of references containing at the end of the tree small arrays of bytes.

在蕙把时间短的对象更是雪上加霜,因为它们不会被压实......因此,你可以有很多的空闲内存运行内存不足,如果CLR是不是能够找到一个空的内存段长足以包含数据的大块。

Putting short duration object in the LOH is even worse, as they won't be compacted... And therefore you could run OutOfMemory with lot of free memory, if the CLR isn't able to find an empty memory segment long enough to contains a large chunk of data.