我在C#中有一个计时器,它在它的方法中执行一些代码。在代码中我使用了几个临时对象。
如果我在方法中有Foo o = new Foo();
之类的东西,这是否意味着每次计时器滴答时,我都在创建一个新对象和对该对象的新引用?
如果我有string foo = null
然后我只是在foo中添加了一些内容,它是否与上面相同?
垃圾收集器是否会删除该对象,并且会不断创建一个或多个引用并保留在内存中?
如果我只声明Foo o;
并且没有将它指向任何实例,那么当方法结束时是不是处理了它?
如果我想确保删除所有内容,最好的方法是:
Foo o;
置于计时器的方法之外,只需在其中进行赋值o = new Foo()
,然后在方法结束后删除指向该对象的指针,垃圾收集器将删除该对象。答案 0 :(得分:32)
1.如果我有类似Foo o = new Foo()的东西;在方法内,做到了 意思是每次计时器滴答时, 我正在创建一个新对象和一个新对象 参考那个对象?
是
2.如果我有字符串foo = null然后我只是在foo中添加了一些时间, 它和上面一样吗?
如果您询问行为是否相同,那么是。
3.垃圾收集器是否删除了对象和引用或 对象不断创建和 留在记忆中?
在将引用视为未使用之后,肯定会收集这些对象使用的内存。
4.如果我只是宣布Foo o;而不是指向任何实例,不是这样 当方法结束时处理?
不,因为没有创建任何对象,所以没有要收集的对象(dispose不是正确的单词)。
5.如果我想确保删除所有内容,那么最好的方法是什么 这样做
如果对象的类实现了IDisposable
,那么你当然希望尽快贪婪地调用Dispose
。 using
关键字使这更容易,因为它以异常安全的方式自动调用Dispose
。
除此之外,除了停止使用该对象之外,您无需做任何其他事情。如果引用是局部变量,那么当它超出范围时,它将有资格进行收集。 1 如果它是类级别变量,那么您可能需要将null
分配给它在包含类符合条件之前使其符合条件。
1 这在技术上是不正确的(或至少有点误导)。一个对象在超出范围之前很久就有资格进行收集。 CLR经过优化,可在检测到不再使用引用时收集内存。在极端情况下,即使其中一个方法仍在执行,CLR也可以收集对象!
<强>更新强>
这是一个示例,演示GC将收集对象,即使它们仍然在范围内。您必须编译一个Release版本并在调试器之外运行它。
static void Main(string[] args)
{
Console.WriteLine("Before allocation");
var bo = new BigObject();
Console.WriteLine("After allocation");
bo.SomeMethod();
Console.ReadLine();
// The object is technically in-scope here which means it must still be rooted.
}
private class BigObject
{
private byte[] LotsOfMemory = new byte[Int32.MaxValue / 4];
public BigObject()
{
Console.WriteLine("BigObject()");
}
~BigObject()
{
Console.WriteLine("~BigObject()");
}
public void SomeMethod()
{
Console.WriteLine("Begin SomeMethod");
GC.Collect();
GC.WaitForPendingFinalizers();
Console.WriteLine("End SomeMethod");
}
}
在我的机器上运行终结器,而SomeMethod
仍在执行!
答案 1 :(得分:15)
.NET垃圾收集器会为您处理所有这些。
它能够确定何时不再引用对象,并且(最终)释放已分配给它们的内存。
答案 2 :(得分:5)
当超出范围变得无法访问时,对象可以进行垃圾回收(感谢ben!)。除非垃圾收集器认为你的内存不足,否则不会释放内存。
对于托管资源,垃圾收集器会知道这是什么时候,您不需要做任何事情。
对于非托管资源(例如与数据库或打开文件的连接),垃圾收集器无法知道它们消耗了多少内存,这就是您需要手动释放它们的原因(使用dispose,或者更好的是使用块)
如果没有释放对象,或者你有足够的内存并且没有必要,或者你在应用程序中维护它们的引用,因此垃圾收集器不会释放它们(如果你实际使用它们)这个参考你保持)
答案 3 :(得分:2)
Foo
变量永远不会占用内存。using
语句只是在IDisposable
对象退出时调用dispose,所以这相当于你的第二个项目符号点。两者都表明您完成了对象并告诉GC您已准备好放弃它。覆盖对象的唯一引用也会产生类似的效果。答案 4 :(得分:2)
让我们逐一回答你的问题。
答案 5 :(得分:2)
以下是快速概述:
最后一件事:如果你宣布Foo foo;
而不指定它,你不必担心 - 没有任何泄露。如果Foo是引用类型,则不会创建任何内容。如果Foo是值类型,它将在堆栈上分配,因此将自动清除。
答案 6 :(得分:1)
垃圾收集器会到处并清理任何不再引用它的东西。除非你在Foo
内部有非托管资源,否则调用Dispose或在其上使用using语句对你没什么帮助。
我很确定这适用,因为它仍然在C#中。但是,我参加了一个使用XNA的游戏设计课程,我们花了一些时间讨论C#的垃圾收集器。垃圾收集很昂贵,因为您必须检查是否有任何对要收集的对象的引用。因此,GC试图尽可能长时间地关闭它。所以,只要你的程序达到700MB时你没有耗尽物理内存,可能只是GC很懒,而且还不用担心它。
但是,如果你只是在循环之外使用Foo o
并且每次都创建o = new Foo()
,那么它应该都可以正常运行。
答案 7 :(得分:1)
正如Brian指出的那样,GC可以收集任何无法访问的内容,包括仍然在范围内的对象,甚至在这些对象的实例方法仍在执行时。请考虑以下代码:
class foo
{
static int liveFooInstances;
public foo()
{
Interlocked.Increment(ref foo.liveFooInstances);
}
public void TestMethod()
{
Console.WriteLine("entering method");
while (Interlocked.CompareExchange(ref foo.liveFooInstances, 1, 1) == 1)
{
Console.WriteLine("running GC.Collect");
GC.Collect();
GC.WaitForPendingFinalizers();
}
Console.WriteLine("exiting method");
}
~foo()
{
Console.WriteLine("in ~foo");
Interlocked.Decrement(ref foo.liveFooInstances);
}
}
class Program
{
static void Main(string[] args)
{
foo aFoo = new foo();
aFoo.TestMethod();
//Console.WriteLine(aFoo.ToString()); // if this line is uncommented TestMethod will never return
}
}
如果使用调试版本运行,附加调试器,或者使用指定的行取消注释TestMethod将永远不会返回。但是没有附带调试器的TestMethod将会返回。