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

时间:2011-05-20 00:08:43

标签: c# memory memory-leaks

我在C#中有一个计时器,它在它的方法中执行一些代码。在代码中我使用了几个临时对象。

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

    < / LI>
  2. 如果我有string foo = null然后我只是在foo中添加了一些内容,它是否与上面相同?

  3. 垃圾收集器是否会删除该对象,并且会不断创建一个或多个引用并保留在内存中?

  4. 如果我只声明Foo o;并且没有将它指向任何实例,那么当方法结束时是不是处理了它?

  5. 如果我想确保删除所有内容,最好的方法是:

    • 使用方法中的using语句
    • 最后通过调用dispose方法
    • Foo o;置于计时器的方法之外,只需在其中进行赋值o = new Foo(),然后在方法结束后删除指向该对象的指针,垃圾收集器将删除该对象。

8 个答案:

答案 0 :(得分:32)

  

1.如果我有类似Foo o = new Foo()的东西;在方法内,做到了   意思是每次计时器滴答时,   我正在创建一个新对象和一个新对象   参考那个对象?

  

2.如果我有字符串foo = null然后我只是在foo中添加了一些时间,   它和上面一样吗?

如果您询问行为是否相同,那么是。

  

3.垃圾收集器是否删除了对象和引用或   对象不断创建和   留在记忆中?

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

  

4.如果我只是宣布Foo o;而不是指向任何实例,不是这样   当方法结束时处理?

不,因为没有创建任何对象,所以没有要收集的对象(dispose不是正确的单词)。

  

5.如果我想确保删除所有内容,那么最好的方法是什么   这样做

如果对象的类实现了IDisposable,那么你当然希望尽快贪婪地调用Disposeusing关键字使这更容易,因为它以异常安全的方式自动调用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)

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

答案 4 :(得分:2)

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

  1. 是的,每当执行此语句时,您都会创建一个新对象,但是,当您退出该方法并且它有资格进行垃圾回收时,它会“超出范围”。
  2. 除了您使用了字符串类型之外,这与#1相同。字符串类型是不可变的,每次进行赋值时都会得到一个新对象。
  3. 是垃圾收集器收集超出范围的对象,除非您将对象分配给具有大范围的变量,例如类变量。
  4. using语句仅适用于实现IDisposable接口的对象。如果是这种情况,通过一切方式使用最适合方法范围内的对象。除非你有充分的理由,否则不要把Foo o放在更大的范围内。最好将任何变量的范围限制在有意义的最小范围内。

答案 5 :(得分:2)

以下是快速概述:

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

最后一件事:如果你宣布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将会返回。