【问题标题】:Why GC does not collect an unused object为什么 GC 不收集未使用的对象
【发布时间】:2016-03-10 20:12:55
【问题描述】:

我试图了解当对象不再被使用时 GC 的行为,我的测试是对对象(使用后)不做任何事情,但它不起作用,对象的析构函数从未被调用。

我创建了一个示例程序,试图等待对象被销毁,但运行 4 小时后没有任何反应。

注意:我知道如果我将对象设置为 null,GC 会收集它,但我只想看看 GC 本身收集对象的“正常”方式。

using System;
using System.Collections.Generic;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    internal class Program
    {
        private const string FilePath = @"C:\objLifeCycle.txt";

        private static void Main(string[] args)
        {
            var result = GetList();
            Console.WriteLine("Results received! Object Id: [{0}] time: [{1}]", result.ObjId, DateTime.Now);

            //result = null;

            Task.Factory.StartNew(() =>
            {
                while (true)
                {
                    Thread.Sleep(2000);
                    Console.WriteLine("...");
                    GC.Collect();
                }
            });

            Console.ReadKey();
        }

        private static LinkList GetList()
        {
            return new LinkList(FilePath) { "link1", "link2", "link3" };
        }
    }
}

internal class LinkList : List<string>
{
    internal string ObjId { get; set; }

    internal string FilePath { get; set; }

    internal LinkList(string filePath)
    {
        ObjId = Guid.NewGuid().ToString();
        FilePath = filePath;

        WriteFile($"Object LinkList with Id [{ObjId}] has been created at [{DateTime.Now}]");
    }

    ~LinkList()
    {
        WriteFile($"Object LinkList with Id [{ObjId}] has been destroyed at [{DateTime.Now}]");

        Environment.Exit(0);
    }

    private void WriteFile(string line)
    {
        var sw = new StreamWriter(FilePath, true);
        sw.WriteLine(line);
        sw.Close();
    }
}

【问题讨论】:

  • 通过向 LinkList 类添加析构函数,实例化的对象会立即提升到 gen1 堆。 Gen1 堆收集不像 gen0 堆收集那样频繁执行,并且由于您可能有足够的可用内存,因此对于此应用程序,它们很可能永远不会发生。

标签: c# garbage-collection c#-6.0


【解决方案1】:

GC 收集对象,但仅限于释放模式。

在调试模式下,变量result 将存在,只要它在范围内,即使你不使用它,这样调试器就可以继续向你显示它的值。但这也意味着该对象不能被垃圾回收。

在 Release 模式下,.Net 足够聪明地意识到该对象将不再被使用,因此它有资格进行垃圾回收。当调用 GC 时(在您的情况下,通过调用 GC.Collect() 显式调用),它将收集它。

【讨论】:

  • The GC will collect the object 这不是一个真实的说法。 允许收集对象,如果它愿意,但它不是必需这样做,即使在释放模式。实现选择这样做,但它也可以很容易地选择不执行该优化。
【解决方案2】:

GC 不会以确定性的方式收集免费资源。程序应该需要内存来请求资源。如果有足够的资源,GC 不会收集任何东西,因为没有必要。此外,GC 只能在程序“空闲”状态下收集内存...

我强烈推荐你阅读:Understanding .NET Garbage Collection

正如您在文档中测试和看到的那样,即使调用 GC.Collect() 也无法强制进行垃圾收集

除此之外,实际上您还需要了解根据平台和 CLR 实现有一些 GC 风格

Cross-VM Object Collections

出于测试目的,您可以使用 GC.AddMemoryPressure Method (Int64) 增加收集内存的需求

【讨论】:

  • 问题中的程序显式调用GC.Collect(),它收集所有代。所以这无关紧要。
  • @svick 但实际上是这样。调用 GC.Collection 不确定地启动收集,但在所示的情况下,如果实际要收集所讨论的变量,即使发生收集,它也是 不确定。 GC允许得出变量不再使用的结论,因此可以清理,但不是必需这样做(无论代码是否在发布/调试版本中编译)。
  • 我已经用补充信息更新了我的答案,解释了为什么 GC.Collect 并不意味着强制垃圾回收
  • @Servy 我认为称它为不确定的,虽然在技术上是正确的,但令人困惑。在具有任何给定设置的任何给定实现上,它很可能是确定性的。所以我认为更好的方法是说它是实现定义的,它是否有资格获得 GC。
  • @svick 我觉得我的第二句话是最清楚的表达方式。 GC允许收集对象,但不是必需这样做。取决于这种情况发生是错误的。
猜你喜欢
  • 2012-01-31
  • 2012-11-10
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-04-21
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多