我试图用简单的代码实现析构函数。
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");
}
}
输出:
正如他所料,在创建Subscriber
的第二个实例时,我可以看到GC正在被收集(终结者被调用)。
注意:在Subscriber的构造函数的else条件中,我在列表中添加较少的项目,如果条件 - 注意应用程序的RAM使用量是否相应减少,是的,它也正在减少。
在其他条件下,我可以将字符串列表留空(因此内存使用量将显着减少)。但这样做,GC没有被收集。很可能是因为M.Aroosi提到了问题的评论。
除了上面所说的,GC只会在一代生成(或由于显式调用)时收集,并且只创建一个对象不会触发它。是的,该对象可以用于最终确定,但GC没有理由收集它。
答案 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