为什么没有为此代码调用析构函数

时间:2018-06-09 14:41:03

标签: c# oop memory-management garbage-collection destructor

我也读过这个articlethis

我试图用简单的代码实现析构函数。

class Program
{
    static void Main(string[] args)
    {
        CreateSubscriber();
        Console.Read();
    }

    static void CreateSubscriber()
    {
        Subscriber s = new Subscriber();
        s.list.Clear();
    }
}

public class Subscriber
{
    public List<string> list = new List<string>();
    public Subscriber()
    {
        for(long i = 0; i < 1000000; i++)
        {
            list.Add(i.ToString());
        }
    }

    ~Subscriber()
    {
        //this line is only performed on explicit GC.Collect()
        Console.WriteLine("Destructor Called - Sub");
    }
}

当代码到达Console.Read()行时,Subscriber的实例不再在范围内(我预计它有资格进行垃圾收集)。我将上面的代码运行了近2个小时,等待destructor Subscriber。但是从未调用过,代码都没有释放内存。

我明白了,在c#中我们无法调用destructors编程,并且它会在Garbage collection上自动调用,所以我尝试显式调用GC.Collect()

通过这样做,我可以看到析构函数被调用。所以在上面的代码中,垃圾收集没有完成!但为什么?

是不是因为,程序是单线程的,并且该线程正在等待Console.Read()上的用户输入?

或者它确实有字符串列表的东西?如果是这样的话

更新(供将来的读者使用)

正如法比安在答案中所说的那样

  

最有可能在某个地方创建新对象并为其分配内存时,GC会检查所有引用并收集第一个对象。

并建议尝试

CreateSubscriber();
Console.Read();
CreateSubscriber();
Console.Readkey();

我更新了如下代码,

class Program
{
    static void Main(string[] args)
    {
        CreateSubscriber(true);
        Console.ReadLine();
        CreateSubscriber(false);
        Console.ReadLine();
    }
    static void CreateSubscriber(bool toDo)
    {
        Subscriber s = new Subscriber(toDo);
        s.list.Clear();
    }
}
public class Subscriber
{
    public List<string> list = new List<string>();
    public Subscriber(bool toDo)
    {
        Console.WriteLine("In Consutructor");
        if (toDo)
        {
            for (long i = 0; i < 5000000; i++)
                list.Add(i.ToString());
        }
        else
        {
            for (long i = 0; i < 2000000; i++)
                list.Add(i.ToString());
        }
        Console.WriteLine("Out Consutructor");
    }
    ~Subscriber()
    {
        Console.WriteLine("Destructor Called - Sub");
    }
}

输出:

enter image description here

正如他所料,在创建Subscriber的第二个实例时,我可以看到GC正在被收集(终结者被调用)。

注意:在Subscriber的构造函数的else条件中,我在列表中添加较少的项目,如果条件 - 注意应用程序的RAM使用量是否相应减少,是的,它也正在减少。

在其他条件下,我可以将字符串列表留空(因此内存使用量将显着减少)。但这样做,GC没有被收集。很可能是因为M.Aroosi提到了问题的评论。

  

除了上面所说的,GC只会在一代生成(或由于显式调用)时收集,并且只创建一个对象不会触发它。是的,该对象可以用于最终确定,但GC没有理由收集它。

1 个答案:

答案 0 :(得分:2)

  

当代码到达Console.Read()的行时,订阅者的实例   不再是范围(我期待它有资格获得垃圾   集合)。

当GC检测到Subscriber实例的引用丢失(超出范围)时,它将标记此对象在下一轮之一上收集。 但是只有GC知道,确切这个下一轮是什么时候。

  

是不是因为,程序是单线程的,并且该线程正在等待   用于Console.Read()的用户输入?

不,如果我们在一个单独的线程中运行此代码,结果将是相同的。 但是,如果我们改变这个:

CreateSubscriber();
Console.Read();

要:

CreateSubscriber();
Console.Read();
CreateSubscriber();
Console.Readkey();

我们可以看到GC将收集垃圾并在Console.Read()之后运行终结器。为什么呢?

最有可能在某个地方创建新对象并为其分配内存时,GC会检查所有引用并收集第一个对象。

让我们总结一下:

  • 当我们只创建一个对象时,代码中没有引用的引用 在程序结束之前,这个obj或它的类 - GC允许程序结束和 退出前收集垃圾。

  • 当我们创建一个对象时,对obj的某种引用 或其类 - GC会执行检查并收集垃圾。

GC运行的方式和时间以及obj的生命周期结束的时间和时间背后有一些复杂的逻辑。

Eric Lippert的answer

引用
  

可以通过各种机制延长寿命,包括   通过lambda捕获外部变量,迭代器块,异步   方法,等等

我们需要在对象的销毁上执行一些代码是非常罕见的。在该特定场景中,不是猜测何时 obj将被销毁,我们可以明确地运行GC.Collect

更常见的是我们可能需要释放一些托管资源,为此我们可以使用IDisposable接口和using语句,在控制流离开块之前自动调用Dispose代码(它将创建一个try {} finally {}子句,最后它将为我们调用Dispose

using(myDisposable) 
{ 
   ... 
}  // dispose is called here