您是否需要处置对象并将其设置为null,还是当垃圾回收器超出范围时清理它们?
Dispose()
!这是这个问题上的一个细微变化,但很重要,因为被处置的对象无法知道它是否“超出范围”(调用Dispose()
不能保证)。此处的更多信息:stackoverflow.com/questions/6757048/…–
您是否需要处置对象并将其设置为null,还是当垃圾回收器超出范围时清理它们?
Dispose()
!这是这个问题上的一个细微变化,但很重要,因为被处置的对象无法知道它是否“超出范围”(调用Dispose()
不能保证)。此处的更多信息:stackoverflow.com/questions/6757048/…–
Answers:
当不再使用对象以及垃圾收集器认为合适时,它们将被清除。有时,您可能需要将对象设置为null
,以使其超出范围(例如,不再需要其值的静态字段),但是总体上通常不需要将其设置为null
。
关于处理对象,我同意@Andre。如果对象IDisposable
是一个好主意,处理它,当你不再需要它,尤其是如果对象使用非托管资源。不处理非托管资源将导致内存泄漏。
您可以使用 using
一旦程序离开using
语句的范围,语句自动处置对象。
using (MyIDisposableObject obj = new MyIDisposableObject())
{
// use the object here
} // the object is disposed here
在功能上等效于:
MyIDisposableObject obj;
try
{
obj = new MyIDisposableObject();
}
finally
{
if (obj != null)
{
((IDisposable)obj).Dispose();
}
}
if (obj != null) ((IDisposable)obj).Dispose();
IDisposable
。不处理对象通常不会在任何设计良好的类上引起内存泄漏。在C#中使用非托管资源时,您应该有一个终结器,该终结器仍将释放非托管资源。这意味着,应该在垃圾回收器最终确定托管对象时才将其推迟到应该完成的时候分配资源。但是,它仍然可能导致许多其他问题(例如未释放的锁)。你应该IDisposable
尽管处置!
对象永远不会像在C ++中那样超出C#的范围。当它们不再使用时,垃圾收集器将自动处理它们。这比C ++更复杂,因为C ++中变量的范围完全是确定性的。CLR垃圾收集器会主动浏览所有已创建的对象,并计算出是否正在使用它们。
一个对象可以在一个函数中“超出范围”,但是如果返回了它的值,则GC将查看调用函数是否保留返回值。
将对象引用设置为 null
无需因为垃圾回收通过确定其他对象正在引用哪些对象来进行工作。
在实践中,您不必担心破坏,它可以正常工作,而且很棒:)
Dispose
IDisposable
完成使用它们时,必须在实现的所有对象上调用它们。通常,您将对using
这些对象使用块,如下所示:
using (var ms = new MemoryStream()) {
//...
}
编辑在可变范围内。Craig询问了变量作用域是否对对象生存期有影响。为了正确地解释CLR的这一方面,我需要解释C ++和C#的一些概念。
在这两种语言中,变量只能在定义的范围内使用-类,函数或用大括号括起来的语句块。但是,微妙的区别是,在C#中,无法在嵌套块中重新定义变量。
在C ++中,这是完全合法的:
int iVal = 8;
//iVal == 8
if (iVal == 8){
int iVal = 5;
//iVal == 5
}
//iVal == 8
在C#中,但是会出现一个编译器错误:
int iVal = 8;
if(iVal == 8) {
int iVal = 5; //error CS0136: A local variable named 'iVal' cannot be declared in this scope because it would give a different meaning to 'iVal', which is already used in a 'parent or current' scope to denote something else
}
如果您查看生成的MSIL,这很有道理-该函数使用的所有变量都在该函数的开始处定义。看一下这个函数:
public static void Scope() {
int iVal = 8;
if(iVal == 8) {
int iVal2 = 5;
}
}
下面是生成的IL。请注意,在if块内部定义的iVal2实际上是在功能级别定义的。实际上,这意味着就变量生存期而言,C#仅具有类和功能级别的范围。
.method public hidebysig static void Scope() cil managed
{
// Code size 19 (0x13)
.maxstack 2
.locals init ([0] int32 iVal,
[1] int32 iVal2,
[2] bool CS$4$0000)
//Function IL - omitted
} // end of method Test2::Scope
每当在堆栈上分配的C ++变量超出范围时,它就会被破坏。请记住,在C ++中,您可以在堆栈或堆上创建对象。当您在堆栈上创建它们时,一旦执行离开作用域,它们就会从堆栈中弹出并被销毁。
if (true) {
MyClass stackObj; //created on the stack
MyClass heapObj = new MyClass(); //created on the heap
obj.doSomething();
} //<-- stackObj is destroyed
//heapObj still lives
在堆上创建C ++对象时,必须显式销毁它们,否则将导致内存泄漏。但是堆栈变量没有这样的问题。
在CLR中,对象(即引用类型)始终在托管堆上创建。对象创建语法进一步加强了这一点。考虑一下此代码段。
MyClass stackObj;
在C ++中,这将MyClass
在堆栈上创建一个实例并调用其默认构造函数。在C#中,它将创建对类的引用,该引用MyClass
不指向任何内容。创建类实例的唯一方法是使用new
运算符:
MyClass stackObj = new MyClass();
在某种程度上,C#对象与使用new
C ++语法创建的对象很像-它们是在堆上创建的,但与C ++对象不同,它们是由运行时管理的,因此您不必担心破坏它们。
由于对象始终在堆上,因此对象引用(即指针)超出范围的事实变得毫无意义。确定对象是否要收集涉及的因素比仅存在对该对象的引用要多。
Jon Skeet 比较了Java中的对象引用附加到气球(即对象)上的字符串进行了比较。同样的类比适用于C#对象引用。它们只是指向包含对象的堆的位置。因此,将其设置为null不会立即影响对象的寿命,气球会继续存在,直到GC“弹出”它。
继续对气球进行类比,似乎合理的是,一旦气球没有附加任何绳子,就可以销毁它。实际上,这正是引用计数对象在非托管语言中的工作方式。除非这种方法不适用于循环引用。想象两个气球用绳子连接在一起,但是两个气球都没有绳子。在简单的引用计数规则下,即使整个气球组是“孤立的”,它们也都继续存在。
.NET对象非常像屋顶下的氦气球。当车顶打开(GC运行)时,即使可能有成组的气球捆绑在一起,未使用的气球也会飘走。
.NET GC将世代GC与标记和清除结合使用。分代方法涉及运行时,它倾向于检查最近分配的对象,因为它们很可能未被使用,而标记和清除则涉及运行时遍历整个对象图,并确定是否存在未使用的对象组。这足以处理循环依赖问题。
另外,.NET GC在另一个线程(所谓的终结器线程)上运行,因为它有很多工作要做,而在主线程上执行该操作将中断您的程序。
正如其他人所说,Dispose
如果该类实现,则您肯定要调用IDisposable
。我对此持严格的立场。例如,有些人可能认为Dispose
on的调用DataSet
是没有意义的,因为他们对其进行了分解并发现它没有做任何有意义的事情。但是,我认为这种说法存在很多谬论。
阅读此文章,以备受尊敬的个人对此话题进行有趣的辩论。然后在这里阅读我的推理,为什么我认为杰弗里·里希特(Jeffery Richter)在错误的阵营中。
现在,您是否应该设置对的引用null
。答案是不。让我用以下代码说明我的观点。
public static void Main()
{
Object a = new Object();
Console.WriteLine("object created");
DoSomething(a);
Console.WriteLine("object used");
a = null;
Console.WriteLine("reference set to null");
}
那么,您何时认为所引用的对象a
符合收集条件?如果您在打完电话后说,a = null
那么您错了。如果您在Main
方法完成后说过,那么您也错了。正确的答案是,有资格在致电的某个时间收集它DoSomething
。没错 在设置引用之前,null
甚至在调用DoSomething
完成之前,它是合格的。这是因为JIT编译器可以识别何时不再取消引用对象引用,即使它们仍然是根的。
a
班上有一个私人成员字段怎么办?如果a
未将其设置为null,则GC无法确定是否a
将以某种方法再次使用它,对吗?因此,a
直到收集了整个包含类,才将其收集。没有?
a
是一个班级成员,并且包含的班级a
仍然植根并在使用中,那么它也将继续存在。这是将其设置null
为有益的一种情况。
Dispose
重要的原因联系在一起- Dispose
在没有根引用的情况下无法在对象上调用(或任何其他不可内联的方法);Dispose
使用一个对象完成调用后,将确保在该对象上执行的最后一个操作的整个过程中,有根引用将继续存在。Dispose
具有讽刺意味的是,放弃对对象的所有引用可能会导致对象资源偶尔被过早释放。
您无需在C#中将对象设置为null。编译器和运行时将负责确定它们何时不在范围内。
是的,您应该处置实现IDisposable的对象。
want
则在完成使用后立即将其清空,以便可以免费回收它。
我同意这里的常见答案,是的,应该处置,不,通常不应将变量设置为null ...,但我想指出的是,处置并非主要与内存管理有关。是的,它可以帮助(有时也可以)帮助进行内存管理,但是其主要目的是为您确定性地释放稀缺资源。
例如,如果您打开一个硬件端口(例如串行),一个TCP / IP套接字,一个文件(处于独占访问模式)或什至是一个数据库连接,现在您将阻止任何其他代码在发布这些项目之前使用这些项目。处理通常会释放这些项目(连同GDI和其他“ os”句柄等,它们有1000个可用,但总体上还是有限的)。如果您不对所有者对象调用dipose并显式释放这些资源,则以后尝试再次打开同一资源(或另一个程序这样做),则打开尝试将失败,因为您未处置,未收集的对象仍具有打开的项目。当然,当GC收集项目时(如果Dispose模式已正确实现),资源将被释放...但是您不知道何时发布,因此您不会 不知道何时可以安全地重新打开该资源。这是Dispose解决的主要问题。当然,释放这些句柄也常常会释放内存,从不释放它们可能永远也不会释放该内存...因此,所有有关内存泄漏或内存清理延迟的讨论。
我已经看到了导致问题的现实例子。例如,我已经看到ASP.Net Web应用程序最终无法连接到数据库(尽管时间很短,或者直到Web服务器进程重新启动),因为sql服务器的“连接池已满” ...即,如此之多的连接已经创建并且在很短的时间内没有被明确释放,以至于无法创建新的连接,并且池中的许多连接(虽然不是活动的)仍然被未分配和未收集的对象引用,因此可以”不能重复使用。正确处理与数据库的连接在必要确保这一问题不会发生(至少不会,除非你有非常高的并发访问)。
如果该对象实现IDisposable
,则可以,请对其进行处理。该对象可能挂在本机资源(文件句柄,OS对象)上,否则本机资源可能不会立即释放。这可能导致资源匮乏,文件锁定问题以及其他本可以避免的细微错误。
另请参见在MSDN上实现处置方法。
Dispose
会被调用。另外,如果您的对象正在使用稀缺资源或正在锁定某些资源(例如文件),那么您将希望尽快释放它。等待GC这样做不是最佳的。
might
打电话,而是will
打电话。
当对象实现时,IDisposable
您应调用Dispose
(或Close
,在某些情况下,它将为您调用Dispose)。
您通常不必将对象设置为 null
,因为GC将知道不再使用对象。
将对象设置为时,有一个例外null
。当我从数据库中检索许多需要处理的对象并将它们存储在集合(或数组)中时。完成“工作”后,我将对象设置为null
,因为GC不知道我已经完成了工作。
例:
using (var db = GetDatabase()) {
// Retrieves array of keys
var keys = db.GetRecords(mySelection);
for(int i = 0; i < keys.Length; i++) {
var record = db.GetRecord(keys[i]);
record.DoWork();
keys[i] = null; // GC can dispose of key now
// The record had gone out of scope automatically,
// and does not need any special treatment
}
} // end using => db.Dispose is called
通常,无需将字段设置为null。我总是建议您处置非托管资源。
根据经验,我还建议您执行以下操作:
我遇到了一些很难找到的问题,这些问题是不遵循上述建议的直接结果。
一个合适的地方是在Dispose()中,但是通常越早越好。
通常,如果存在对对象的引用,则垃圾收集器(GC)可能需要花费几代时间才能确定不再使用该对象。对象始终保留在内存中。
在您发现应用程序使用的内存比您预期的要多得多之前,这可能不是问题。发生这种情况时,请连接内存分析器以查看未清除的对象。将引用其他对象的字段设置为null并清除处置集合,可以真正帮助GC确定可以从内存中删除哪些对象。GC可以更快地回收使用的内存,从而使您的应用程序少很多内存且更快。
总是打电话处理。不值得冒险。大型托管企业应用程序应得到尊重。无法做任何假设,否则它会再次咬你。
不要听嬉皮士。
许多对象实际上并没有实现IDisposable,因此您不必担心它们。如果它们确实超出范围,它们将自动释放。另外,我从未遇到过必须将某些内容设置为null的情况。
可能发生的一件事是许多对象可以保持打开状态。这会大大增加应用程序的内存使用率。有时很难弄清楚这是否实际上是内存泄漏,或者您的应用程序是否正在做很多事情。
内存配置文件工具可以帮助解决类似问题,但这可能很棘手。
此外,请始终取消订阅不需要的事件。还应注意WPF绑定和控件。这不是通常的情况,但是我遇到了一个WPF控件绑定到基础对象的情况。基础对象很大,并且占用了大量内存。WPF控件已替换为新实例,而旧实例由于某种原因仍在徘徊。这导致大量内存泄漏。
后面的代码编写得很差,但是关键是要确保不使用的内容超出范围。使用内存分析器花了很长时间才找到它,因为很难知道内存中的什么是有效的,什么不应该存在。