类实例是否会过早地被垃圾收集?(我什至不知道我的问题是否有道理;这只是我不明白的事情,并且已经在我脑海中盘旋了一段时间)考虑使用以下类:publicclassMyClass{privateint_myVar;publicvoidDoSomething(){//...做一些事情..._myVar=1;System.Console.WriteLine("里面");}}并使用这样的类:publicclassTest{publicstaticvoidMain(){//...一些代码...System.Console.WriteLine("Before");//没有赋值给变量。新的MyClass().DoSomething();//...一些其他代码...System.Console.WriteLine("After");(Ideone)上面,我创建了一个类的实例,但没有将它分配给变量。我担心垃圾收集器可能会过早删除我的实例。我对垃圾回收的幼稚理解是:“删除一个对象,只要没有指向它的引用”。因为我创建了我的实例而没有将它分配给一个变量,所以这是真的。显然代码运行正确,所以我的假设似乎是错误的。有人能告诉我我错过了什么吗?总而言之,我的问题是:(为什么/为什么不)实例化一个类而不将其作为变量或返回它是否安全?即newMyClass().DoSomething();和varc=newMyClass();c.DoSomething();从垃圾收集的角度来看是一样的吗?这有点安全。或者更确切地说,它就像您拥有一个在方法调用后未使用的变量一样安全。当GC可以证明没有任何对象在使用任何数据时,一个对象就有资格进行垃圾收集(这不同于说它将立即被垃圾收集)。如果该方法不使用当前执行点之外的任何字段,即使在执行实例方法时也会发生这种情况。这可能非常令人惊讶,但通常不是问题,除非您有一个终结器,这在当今很少见。当您使用调试器时,垃圾收集器对于收集的内容会更加保守。这是“早期收集”的演示-在这种情况下,早期完成是因为它更容易演示,但我认为它证明了这一点:usingSystem;使用系统线程;类EarlyFinalizationDemo{intx=Environment.TickCount;~EarlyFinalizationDemo(){Test.Log("Finalizercalled");}publicvoidSomeMethod(){Test.Log("EnteredSomeMethod");GC.收集();GC.WaitForPendingFinalizers();线程.睡眠(1000);Test.Log("收集了一次");Test.Log("x的值:"+x);GC.收集();GC.WaitForPendingFinalizers();线程.睡眠(1000);Test.Log("退出一些方法");}}classTest{staticvoidMain(){vardemo=newEarlyFinalizationDemo();演示。一些方法();测试。日志(“一些方法完成了”);线。睡眠(1000);测试。Log("主要完成");}publicstaticvoidLog(stringmessage){//确保所有日志条目都间隔开lock(typeof(Test)){Console.WriteLine("{0:HH:mm:ss.FFF}:{1}",DateTime.现在,消息);线程.睡眠(50);}}}输出:10:09:24.457:输入SomeMethod10:09:25.511:收集一次10:09:25.562:Vx的值:7347928110:09:25.616:Finalizer调用10:09:26.666:退出SomeMethod10:09:26.717:SomeMethod完成10:09:27.769:Main完成请注意在打印x的值后对象是如何完成的(因为Weneedtheobjecttoretrievex)但在SomeMethod完成之前其他答案都很好,但我想在这里强调几点。这个问题基本上可以归结为:垃圾收集器何时可以推断出给定对象已死?答案是垃圾收集器有很大的自由度来使用它选择的任何技术来确定对象何时死亡,而这种广泛的自由度会导致一些令人惊讶的结果。那么让我们开始吧:我对垃圾收集的天真理解是:“一旦没有指向它的引用就删除一个对象”。这种理解是错误的。假设我们有classC{Cc;公共C(){this.c=this;现在C的每个实例都有一个存储在其自身中的引用。如果仅当对象的引用计数达到零时才回收对象,则永远不会清除循环引用的对象。正确的理解是:某些引用是“已知根”。当收集发生时,跟踪已知的根。就是说,所有已知的根都是活的,凡是被活物所指的,也是活的、过渡的。其他一切都已经死了,可以回收了。不收集需要终结的死对象。相反,它们在终结队列中保持活动状态,这是一个已知的根,直到它们的终结器运行,之后它们被标记为不再需要完成。未来的集合会第二次将它们识别为已死,并且它们将被驱逐。许多事物都是已知的根源。比如静态字段都是已知根。局部变量可能是已知的根,但正如我们将在下面看到的,它们可以以令人惊讶的方式进行优化。临时值可以是已知的根。我正在创建一个类的实例而不将其分配给变量。您在这里的问题很好,但它基于一个错误的假设,即局部变量始终是已知的根。分配对局部变量的引用不一定使对象保持活动状态。垃圾收集器可以自由优化局部变量。让我们举个例子:voidM(){varresource=OpenAFile();inthandle=resource.GetHandle();UnmanagedCode.MessWithFile(句柄);}假设资源是一个带有终结器的类的实例,终结器关闭文档。终结器可以在MessWithFile之前出现吗?是的!资源是具有整个M体生命周期的局部变量这一事实是无关紧要的。运行时可以意识到这段代码可以优化为:voidM(){inthandle;{var资源=OpenAFile();handle=resource.GetHandle();}UnmanagedCode.MessWithFile(handle);现在资源正在调用MessWithFile已经死了。终结器在GetHandle和MessWithFile之间运行不太可能但合法,现在我们正在处理关闭的文件。此处正确的解决方案是在调用MessWithFile后在资源上使用MessWithFile。回到你的问题,你关心的基本上是“具有已知根的引用的临时位置?”答案通常是肯定的,同时需要注意的是,如果运行时可以确定引用永远不会被解析引用,则允许它告诉GC引用的对象可能已经死亡。换句话说:你问的是newMyClass().DoSomething();和varc=newMyClass();c.DoSomething();从GC的角度来看是相同的。是的。在这两种情况下,无论局部变量c的生命周期如何,GC都可以在确定可以安全地终止对象时终止该对象。对您问题的简短回答:相信垃圾收集器。做正确的事写得很好。唯一需要担心GC做错事的时候就像我提到的那样,终结器的计时对于非托管代码调用的正确性很重要。当然,GC对您来说是透明的,不会发生早期收集。所以我想你想知道实现细节:实例方法的实现就像带有附加this参数的静态方法一样。在您的情况下,this值存在于寄存器中并像这样传递给DoSomething。GC知道哪些寄存器包含活动引用并将它们视为根。只要DoSomething可能仍然使用这个值,它就会一直存在。如果DoSomething从不使用实例状态,它实际上可以在方法调用仍在实例上运行时收集实例。这是不可观察的,因此是安全的。只要您谈论的是单线程环境,您就是安全的。如果您在DoSomething方法中启动一个新线程,有趣的事情就会开始发生,如果您的类有终结器,就会发生更多有趣的事情。这里要理解的关键是你和运行时/优化器/等之间的许多契约只在一个线程中有效。当您开始使用一种主要不是面向多线程的语言(是的,C#是其中一种语言)在多线程上进行编程时,这就是会产生灾难性后果的事情之一。在你的例子中,你甚至使用了this实例,这使得意外收集的可能性降低,同时仍在方法中;无论如何,合约是在单线程上,你无法观察到优化和未优化代码之间的差异(除了内存使用、速度等,但那些都是“免费午餐”)。以上是C#学习教程:没有赋值给变量的类实例会被过早地垃圾回收吗?如果所有分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处:
