在C中释放内存的正确方法是什么?#

问题描述:

我在C#中有一个计时器,它在其方法中执行一些代码。在代码中我使用了几个临时对象。在C中释放内存的正确方法是什么?#

  1. 如果我有像Foo o = new Foo();的方法中,这是否意味着每个计时器滴答声的时候,我正在创建一个新的对象和新的引用该对象?

  2. 如果我有string foo = null然后我只是把时间放在foo里,是否和上面一样?

  3. 垃圾回收器是否永远删除对象,引用或对象是否不断创建并保留在内存中?

  4. 如果我只是声明Foo o;而不是指向任何实例,那么在方法结束时是不是放置了?

  5. 如果我想确保一切都被删除,什么是做的最好的方式:通过在年底

  6. 调用Dispose方法

    • 与方法
    • 内using语句通过将Foo o;放在定时器的方法之外,并将其分配o = new Foo()在里面,那么在方法结束后删除指向该对象的指针,垃圾收集器将删除该对象。

1.如果我有类似Foo o = new Foo();里面的方法,是否 的意思是每次计时器滴答, 我正在创建一个新的对象和一个新的 引用该对象?

是的。

2.如果我有字符串foo = null,然后我只是把时间放在foo中, 是否与上面相同?

如果你问的行为是否相同,那么是的。

3.Does垃圾收集永远删除对象和基准或 对象的持续创建和 留在记忆?

这些对象所使用的内存在引用被视为未被使用后肯定会被收集。

4.如果我只是声明Foo o;而不是指向任何实例,当方法结束时不是 ?

不,因为没有对象被创建,所以没有收集对象(处置不是正确的词)。

5。如果我想确保一切都被删除,什么是 做的最好的方式,

如果对象的类实现IDisposable那么你一定要尽快拨打贪婪Disposeusing关键字使得这个更容易,因为它以自动异常安全的方式调用Dispose

除此之外,除了停止使用该对象之外,实际上没有其他事情需要做。如果引用是局部变量,则当它超出范围时,它将有资格收集。 如果它是一个类级别的变量,那么您可能需要分配null才能使其符合条件。


这在技术上是不正确的(或者至少有点误导)。一个对象在超出范围之前可能有资格收集。 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

如果没有该根对象的引用,对象的方法会如何? – Yaur 2011-05-20 06:06:44

+0

@Yaur:好问题:考虑一个不使用其他实例成员(变量或方法)的实例方法。这意味着只需要提取对象引用来传递'this'引用。在此之后,只要CLR可以检测到该对象即使它仍然可以被植根,它也不会在以后使用,在技术上符合条件。 – 2011-05-20 13:12:44

+0

@Brian Gideon我明白你的观点,但我对此表示怀疑,因为CLR使用mark-and-sweep来确定GC是否符合这种情况,或者该对象甚至在技术上符合垃圾回收的“合格条件”。但无论如何,这是一个足够有趣的案例来做一些测试。 – Yaur 2011-05-20 13:34:52

.NET垃圾回收器为您处理所有这些事情。

它能够确定何时不再引用对象,并将(最终)释放已分配给它们的内存。

+0

您确定连计时器内创建的对象都被删除吗?因为我遇到了一个问题,那就是我的应用程序在10分钟内是700MB,而且我猜测应该在计时器中删除的对象出现问题。 – user579674 2011-05-20 00:16:06

+0

除非你正在做一些事情来保留那些在定时器的“打勾”方法范围内创建的引用,否则它们将在方法退出后的某个时间*被释放。这是垃圾收集器运行时检测到没有任何东西仍然指向这些对象。如果你的计时器在足够快的周期内进行滴答,我可以看到如何消耗大量的内存。 – Yuck 2011-05-20 00:19:54

+3

它处理所有这些......除非它没有。不用担心内存管理是一个失控堆增长的好方法。 – 2011-05-20 00:23:42

对象一旦他们 超出范围 成为无法访问(谢谢本!),可以进行垃圾回收。除非垃圾收集器认为你的内存不足,否则内存不会被释放。

对于托管资源,垃圾收集器将知道这是什么时候,并且您不需要执行任何操作。

对于非托管资源(例如到数据库或打开文件的连接),垃圾收集器无法知道它们消耗了多少内存,这就是为什么您需要手动释放它们(使用dispose或更好使用块)

如果对象没有被释放,或者你有足够的内存并且没有必要,或者你在应用程序中维护对它们的引用,因此垃圾收集器不会释放它们(如果你真的使用这个参考你保留)

+2

s /超出范围/无法访问/。范围实际上只与(未捕获的)值类型的局部变量有关。 – 2011-05-20 00:22:33

+0

关于您提到的打开的文件,磁盘上的文件大小与文件在内存中的内存量相同(假定它未被更改)。 – user579674 2011-05-20 00:29:06

+0

除非您将整个文件读入内存。当你打开一个文件时,操作系统可以用不同的方式锁定它(读/写等)。垃圾收集是非确定性的(即它在需要时发生),所以如果文件句柄没有明确释放,文件可能会长时间保持锁定状态。文件句柄也是有限的,垃圾收集器不知道任何这些,这就是为什么文件应该关闭,而不是留给垃圾收集器。 – 2011-05-20 00:36:07

  1. 是的
  2. 你是什么意思?每次运行该方法时都会重新执行。
  3. 是的.Net垃圾回收器使用一种算法,该算法以任何全局/范围内的变量开始,在它跟随它递归查找的任何引用后遍历它们,并删除内存中任何被认为无法访问的对象。see here for more detail on Garbage Collection
  4. 是的,方法中声明的所有变量的内存在方法退出时释放,因为它们都是不可访问的。此外,任何已声明但从未使用的变量都将由编译器进行优化,因此实际上您的变量永远不会占用内存。
  5. using声明只是简单地在对象退出时调用IDisposable对象,所以这相当于您的第二个项目符号点。两者都会表明您已完成对象并告诉GC您已准备好放弃它。覆盖对象的唯一引用将会产生类似的效果。
+1

CrazyJugglerDrummer提供的链接是一个非常棒的GC阅读。 – 2011-05-20 00:32:45

垃圾收集器将来到并清理不再引用它的任何东西。除非在Foo中有非托管资源,否则调用Dispose或在其上使用using语句不会真的有帮助。

我很确定这适用,因为它仍然在C#中。但是,我使用XNA开发了一个游戏设计课程,我们花了一些时间讨论C#的垃圾收集器。垃圾收集很昂贵,因为您必须检查是否有任何对要收集的对象的引用。所以,GC试图尽可能延长这一点。所以,只要你没有耗尽物理内存,当你的程序达到700MB时,它可能就是GC懒惰,不担心它。

但是,如果您只是在循环外部使用Foo o,并且每次都创建o = new Foo(),则它应该都可以正常工作。

让我们一一回答你的问题。

  1. 是的,你让每当执行该语句一个新的对象,但是,它会“超出范围”当您退出方法,它是符合垃圾回收。
  2. 那么除了你使用了一个字符串类型之外,这和#1是一样的。字符串类型是不可变的,每当你做任务时你会得到一个新对象。
  3. 是垃圾收集器收集超出范围对象,除非您将该对象分配给具有大范围的变量,例如类变量。
  4. 是的。
  5. using语句只适用于实现IDisposable接口的对象。如果是这种情况,通过一切手段最适合方法范围内的对象。除非你有足够的理由这么做,否则不要把Foo o放在更大的范围内。最好将任何变量的范围限制在有意义的最小范围内。

下面是一个简单的概述:

  • 一旦引用都消失了,你的对象将可能被垃圾收集。
  • 你只能依靠统计收集来保证你的堆大小正常,只要所有引用垃圾的地方都消失了。换句话说,不能保证一个特定的对象会被垃圾收集。
    • 因此,您的终结器也永远不会被保证被调用。避免终结者。泄漏的
  • 两种常见来源:
    • 事件处理程序和代表们引用。如果您订阅了一个对象的事件,那么您正在引用它。如果您有一个委托给对象的方法,则引用它。
    • 非托管资源按定义不会自动收集。这是IDisposable模式的用途。
  • 最后,如果您想要一个不妨碍对象被收集的引用,请查看WeakReference。

最后一件事:如果你声明Foo foo;没有分配它,你不必担心 - 没有泄漏。如果Foo是引用类型,则不会创建任何内容。如果Foo是一个值类型,它将被分配到堆栈上,因此会自动清理。

正如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将会返回。