65.9K
CodeProject 正在变化。 阅读更多。
Home

.NET 中的对象生命周期和垃圾回收

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.31/5 (9投票s)

2004 年 3 月 3 日

2分钟阅读

viewsIcon

97213

本文讨论了我们需要关注对象生命周期,而不能依赖 JIT 标记对象进行垃圾回收的场景。

引言

托管应用程序和垃圾回收机制宣称开发者无需关心内存分配和对象生命周期,而是将它们交给垃圾回收器处理。JIT 编译器有自己的想法,会在底层优化代码。这使得开发者无法得知对象的销毁时刻。虽然我们不知道对象的销毁时刻,但仔细的规划和设计仍然是必要的,以便与垃圾回收器协同工作,避免出现问题。

问题

猜测对象何时何地被标记为垃圾回收是很困难的。考虑以下代码

namespace NS
{
    class A
    {
        public static void Main()
        {
            System.String s;
            s = "some test string"; 
            // <- is s marked for collection after this ?
            
            System.Int32 i;
            i = 10;
        }    // <- is s marked for collection here ? 
    } 
}

JIT 标记字符串 s 进行垃圾回收的确切位置是未知的。当我们需要控制在应用程序生命周期内都需要使用的对象时,这尤其成问题。

示例解决方案

一个常见的例子是互斥锁,它用于确保应用程序只有一个实例正在运行。以下代码说明了这一点

namespace NS
{
    class SomeFormClass:System.Windows.Forms.Form
    { 
        public static void Main()
        {
            bool created;
            System.Threading.Mutex m = new 
                System.Threading.Mutex(true, "OnlyOneApp", out created);
            if (! created)
                return; 
            // <- mutex may be marked for garbage collection here ! 
            // if the application runs for long and mutex
            // is garbage collected, the design fails
            System.Windows.Forms.Application.Run(new SomeFormClass()); 
            System.GC.KeepAlive(m); 
            // <- this will keep the mutex alive till this point 
        }
    }
}

如果缺少 GC.KeepAlive() 语句,JIT 编译器可能会优化并标记互斥锁在 Application.Run() 语句之前进行垃圾回收。GC.KeepAlive() 语句会使对象存活到该语句为止。在上述场景中,我们知道互斥锁的确切创建位置以及它需要的持续时间。另一个要点是,变量在这些已知点都可以访问。考虑一个对象在整个应用程序中都需要使用,但其实例化点未知的情况。此外,假设没有单个“存活”对象会持有对该所需对象的引用,直到应用程序的生命周期结束。这种情况下,JIT 会在对象超出作用域的尽可能早的时间点将其标记为垃圾回收。下面的代码说明了一种保持这类对象存活的设计,并指出了如何在控制台和 Windows 应用程序中处理这种情况。最重要的是,在应用程序退出时使用 GC.SuppressFinalize() 方法,否则应用程序将进入无限循环并挂起。

namespace GCDemo
{ 
    // class used to test the scenario
    class StartTest
    {
        // following delegate and event are required by console apps
        // In Windows apps, this will be taken care by ApplicationExit event
        internal delegate void ApplicationExit();
        static internal event ApplicationExit AppExit;
        public static void Main()
        {
            for (int i=0; i<3; i++)
            {
                new GCDemo(); // <- creating 3 objects here as a test case
            } 
            // <- we expect the JIT to mark for
            // garbage collect these objects atleast here
 
            // following lines are required for a console app,
            // Application.ApplicationExit event is raised by windows apps
            if (AppExit != null) 
                AppExit();
        }
    }
    // this class object should be kept alive for the entire application life
    public class GCDemo
    {
        public GCDemo()
        {
            // register with the application exit event
            // AppExit is my own event for console applications
            // See the declaration in StartTest class above
            StartTest.AppExit += new StartTest.ApplicationExit(AppExiting);

            // for windows app use the line below by 
            // implementing a EventHandler delegate
            // Application.ApplicationExit += new EventHandler(eventhandler);
        }

        ~GCDemo()
        {
            // ReRegisterForFinalize will put the entry back in the queue
            // and resurrect the object 
            System.GC.ReRegisterForFinalize(this);
        }
        void AppExiting()
        {
            // suppressing the finalization will
            // stop the application from hanging 
            System.GC.SuppressFinalize(this);
        }
    }
}

有时,在分配大量内存之前,值得调用 GC.Collect()GC.WaitForPendingFinalizers(),就像下面所示,尤其是在内存紧张的情况下。

        ......
        GC.Collect();
        GC.WaitforPendingFinalizers();
        for (int i=0; i<1000; i++)
        {
                somelist.Add(new someobject());
        }
        ......
© . All rights reserved.