我发现 c# / xna 中的渲染目标不会自动释放,您必须调用 .dispose() 成员才能摆脱它们。
我认为垃圾收集应该在所有引用都消失后自动摆脱它们,这是什么?
还有什么不是自动处理的吗?
我发现 c# / xna 中的渲染目标不会自动释放,您必须调用 .dispose() 成员才能摆脱它们。
我认为垃圾收集应该在所有引用都消失后自动摆脱它们,这是什么?
还有什么不是自动处理的吗?
我认为垃圾收集应该在所有引用都消失后自动摆脱它们,这是什么?
这在两个方面是不正确的。
垃圾收集收集可以安全收集和需要的托管内存。就是这样。
将垃圾收集视为模拟无限堆内存的一种方式。既然我们可以假装我们有无限的内存,我们就不必调用任何东西来释放我们用完的内存,因为我们为什么要保留无限的资源?*
GC 模拟无限堆的最简单方法是什么都不做。这在例如进程有 4GiB 可用内存并且它使用 50MiB 时有效。收集永远不会出现。如果应用程序足够小以至于永远不会发生集合,这确实会发生。(虽然它不会懒到让你在没有集合的情况下使用 megs,但它会在尝试向操作系统询问应用程序的更多内存之前尝试收集,但当你想知道“为什么不使用 GC”时它仍然很有用。 ..” 在某些时候什么都不做可能是一种有效的 GC 方法,一旦你想到了这种可能性,很多其他问题就会消失)。
另一种方法是在可以清理的那一刻立即清理所有东西。这发生在引用计数垃圾收集器上;这与 .NET 无关,但值得一提,因为它与您的问题的“在所有引用都消失后”非常匹配。
另一个是当需要内存,并且在已经空闲内存的存储中不可用时,GC 会停止所有线程,识别根(静态变量和每个线程堆栈中的本地引用),识别根引用的所有内容,以及那些引用的所有内容,依此类推,直到找到仍然可以被应用程序使用的所有内容,然后将其他所有内容占用的内存视为空闲。然后它会压缩所有没有释放内存的对象,这样既可以避免在释放更多可用内存时产生碎片,又可以使对象在内存中更紧密地结合在一起(这对性能的影响很小)。
如果还“提升”它没有删除的对象,因为如果第一次删除它是不正确的,那么下一次就不会了,所以它会更少地查看那些在这个过程中幸存下来的对象经常。
此时需要注意两点:
获取然后释放托管内存当然只是我们希望先做某事然后撤消它的一种情况。其他例子是:
到目前为止,我们所描述的 GC 对这些都没有帮助。
尽管如此,它们都有两个共同点。它们有一个开始操作和一个结束操作。
开始操作要么很好地映射到对象创建,要么映射到某个方法调用。
结束操作可以匹配Close()
, End()
, Free()
,Release()
方法调用,但在定义时IDisposable.Dispose()
我们可以给它们一个通用接口。该语言还可以通过using
†添加一些帮助。
(一个类可能同时具有 aClose()
和 a Dispose()
。在这种情况下,我们既可以选择关闭我们稍后将重新打开或以其他方式在其关闭状态下使用的东西,也可以选择在我们完成后确保清理的方法目的)。
因此,通过这种方式,IDisposable.Dispose()
存在清理除托管内存之外的所有需要清理的内容。
现在,在这种情况下,需要实现三种类型的类IDisposable
:
IDisposable
因此当我们处置此类的对象时,它会调用Dispose()
这些字段。让我们考虑一下如果 GC 释放了这样一个对象的内存并且Dispose()
没有被调用会发生什么。
在第三种情况下,实际上没有处理对象并不重要。真正重要的是这些字段没有被处理(或者也许这无关紧要,但有些字段很重要)。
在第二种情况下,它的重要性取决于池的重要性。这可能不是最理想的,但不是世界末日。
在第一种情况下,这是一场灾难——我们有一个未释放的资源,在应用程序结束之前我们不可能释放它,甚至可能在应用程序结束之后(取决于资源的性质)。
出于这个原因,对象可以有终结器。
当 GC 即将释放一个对象的内存时,如果它有一个终结器,并且如果该终结器没有被抑制(Dispose()
通常会这样做以表明该对象已被很好地清理并且不需要对其进行更多工作),然后不是从对象中释放内存,而是将其放入终结队列。这当然意味着该对象不仅没有收集到它的内存,而且也没有任何可以通过它的字段访问的对象。
终结器线程通过此队列工作,在每个队列上调用终结器方法。
发生这种情况有两个不好的地方:
编辑:请注意,我们没有第三类课程的终结者,也许没有第二类课程。在这种情况下,不需要终结器,因为它作为字段具有的真正关键的对象将调用其终结器,它完成了重要的工作。如果您尝试从终结器中处理可终结的字段,也很容易得到一个严重的错误。如果您编写了一个一次性类,它包装了它“拥有”的一个或多个一次性字段并负责清理,那么请实现IDisposable
,但不要添加终结器。
总之,调用 finalizer 意味着以下两种情况之一:
因此,虽然 GC 与除托管内存之外的资源之间通过终结器进行交互,但它是最后手段的交互,无论如何它都不可靠。您不应该将终结器视为使 GC 进行清理的一种方式,而是一种让 GC 在缺陷意味着它没有发生的情况下不让清理变得不可能的方式(以及一种清理的方式) -up 应用程序关闭)。
*当然,如果你认为你拥有无限的资源(鱼、水牛、海洋的废物处理能力)而事实证明你没有,那么事情就会变得一团糟,所以也许不要将这条规则应用于生活。
†<code>using 使调用Dispose()
更简单
using(someDisposableObject)
{
//Do Stuff
}
相当于:
try
{
//Do Stuff
}
finally
{
if(someDisposableObject != null)
((IDisposable)someDisposableObject).Dispose();
}
和:
using(var someDisposableObject = someMethodCallOrCallToNew())
{
//Do Stuff
}
相当于:
var someDisposableObject = someMethodCallOrCallToNew();
try
{
//Do Stuff
}
finally
{
if(someDisposableObject != null)
((IDisposable)someDisposableObject).Dispose();
}
在编译器可以确定 someDisposableObject 不可能为空的情况下,可以删除空检查作为优化。
Dispose
即使使用自动垃圾收集,当您确定不会再次使用该实例时调用它仍然是一个好主意。
或者使用using
语句。
这样,您可以选择何时“处置”对象,而不是等到垃圾收集器执行此操作。
存在的原因IDisposable
是支持垃圾收集无法[有效]管理对象内存的异常情况。它存在的主要原因是为了释放非托管资源。
这种情况的常见情况之一是与非托管内存的交互。当一个对象正在做一些涉及在垃圾收集器范围之外分配内存的事情时,垃圾收集器就不能负责清理它;它需要由程序员处理。这些情况也更有可能分配大量内存,因此确保更快地清理它变得更加重要。
它也用于对象需要进行某种“清理”而不是实际释放内存的情况。示例是处理 IO 时的文件处理程序,或与应关闭的数据库的连接。让垃圾收集器释放对象的内存不会执行这种类型的清理。
在某些情况下,程序员使用IDisposeable
接口“劫持”using
语句的语法,实际上并没有在它的 dispose 方法中处理非托管资源。
我的观点是,如果一个对象有一个 Dispose 方法,它可能需要被释放。我通常尝试将它放在一个带有“使用”语句的块中。这将在块的末尾自动调用 Dispose 方法。