asp.net C#基础知识之垃圾回收机制介绍

第一节 垃圾回收机制早期的C/C++开发中,一个对象的生命周期大概像这样:计算对象大小——查找可用内存——初始化对象——使用对象——摧毁对象。如果在上面的过程中,开发人员忘记了“摧毁对象”这一步骤,则很有可能导致内存泄露!这是一个非常可怕的事情!幸好,CLR的开发人员为我们解决了这一问题,在.NET Framework中引入了垃圾回收机制,使得开发人员不需要再过多地关注内存释放的问题,CLR会在合适的时候进行执行垃圾回收来释放不再使用的内存。这里就像一个邪恶的男人所说的话:给我一个女人,我能创造一个民族!其实一个新世界你都可以去创造,前提是要有一个足够大的星球内存来容纳你的子孙!CLR就是这么认为的。

在激活一个进程时,CLR会先保留一块连续的内存,在主线程启动过程中,可能会初始化一系列对象,CLR先计算对象大小及其开销所占用的字节数,接着会在连续的内存块中为这些对象分配内存,这些对象被配置在第0代内存,在构造第0代内存的时候会分配一个默认大小的内存,随着程序的运行,可能会初始化更多的对象,CLR发现第0代内存不能装载更多的新生对象,此时CLR会启动垃圾回收器对第0代内存进行回收,不再使用的对象所占用的内存会被释放,接着把0代对象提升为第1代,然后把新生对象配置在第0代内存区中。CLR使用了3个阶段的代,每次新分配的对象都会被配置在第0代内存中,最老的对象在第2代内存中,每次为新对象分配内存时,都可能会进行垃圾回收以释放内存,很显然CLR认为“内存永远也使用不完”,很显然CLR为我们自动管理了内存垃圾,很显然CLR的这个“认为”在我们开发人员看来是不成立的,我们从以下几个方面来解读垃圾回收机制。

 

第二节 内存分配垃圾回收是对引用类型而言的。

CLR要求引用类型的对象从托管堆中分配内存的,值类型是从栈中分配内存。在C#中通常使用new操作符来创建一个对象,编译器将会在IL中生成newobj指令,执行一个newobj指令会有以下过程:(在前一节中我们已经知道,在一个进程启动时会先保留一个连续的内存块)先计算类型及其基类型的字段所需要的字节数A,再计算类型对象的指针和一个同步索引块共8或16个字节,到此总共需要(A+8或18)字节的内存,CLR会检查当前进程区是否有足够的内存来容纳(A+8或16)个字节的对象,如果有,则将新对象放其中,否则CLR进行垃圾回收,释放不再使用的内存来容纳新的对象,在整个进程的生命周期中,CLR会维护一个指针P,它一直指向当前进程所分配的最后一个对象内存的结尾处而不会跑出当前进程内存区边界,如图:

 

每次计算新的将要创建的对象所需要的字节数时,CLR都是通过P加上新的需要的字节数进行检查可用内存区,如果超出了地址末尾,则表示当前的托管堆已经被用完,准备进行垃圾回收了。由于进程拥有一个独立连续的内存区,所以CLR能保证创建的新对象基本上都是紧挨着放置的。

 

第三节 代当托管堆的内存被用完,新生的对象无处放置时,CLR就要开始进行垃圾回收了,随着程序的持续运行,托管堆可能越来越大,如果要对整个托管堆进行垃圾回收(下面会讲到如何回收),势必会严重影响性能,因为有时可能仅仅需要数十个字节就能容纳新的对象,有时候可能要对可达的对象进行搬迁,为了小范围有目的性地进行垃圾回收,CLR使用了“代”概念来优化垃圾回收器,代是垃圾回收机制使用的一个逻辑技术,也是一种算法,它把托管堆中的内存分为3个代(截止到目前.NET Framework4.0有3个代:0、1、2)。

进程在初始化时,CLR为托管堆初始化为包含0个对象的一块内存区域,新添加到堆中的对象为第0代对象,CLR在初始化第0代内存区时会分配一个默认的配额,假设为512K,不同的.NET框架和版本,可能这个配额不相同。假设进程及其线程初始化完成后分配了4个对象,如下图:

 

这4个对象占据了512K的内存,程序继续运行,当再分配第5个对象Obj5的时候,发现第0代已无可用内存,此时CLR会启动垃圾回收器进行垃圾回收,假如上面的Obj3已经无效,此是Obj3的内存会被释放出来,接着搬迁Obj4对象到Obj3的位置(在Obj2的内存地址末尾处),存活下来的对象Obj1、Obj2和Obj4会被提升为第1代对象,第1代的内存区域根据程序运行的情况,CLR可能会为其分配20M(也可能是其他值)大小的内存区,第0代内存暂时为空,接着将Obj5分配到第0代内存区,如下:

 

程序继续运行,并又新分配了4个对象Obj6-Obj9,且此时Obj2和Obj5都不再使用,即为不可达对象,此时需要再创建一个新对象Obj10,但发现第0代的512K内存已经用完,所以CLR再一次启动垃圾回收器进行垃圾回收,这一次垃圾回收器会认为第0代的新对象生命周期短,所以先对第0代进行回收,并将存活对象提升到第1代中,垃圾回收器发现此时第1代中的对象远远小于20M,所以放弃对第1代的回收,程序继续运行,分配N多的新对象,当把第0代的对象提升到第1代,而第1代对象超20M时,则会对第1代的对象进行回收,第1代存活的对象被提升为第2代,第0代存活的对象被提升为第1代,如下图:

 

每一次垃圾回收的过程,垃圾回收器会根据实际使用情况自动调整第0、1、2代的默认配额大小,比如可能将第2代调整为200M,几分钟过后可能将其调整为120M,也有可能是1024M,程序继续运行,当对3个全部进行了垃圾回收且重新调整配额后,可用内存还不足以放置新对象,CLR就会抛出OutOfMemoryException异常,此时活神仙也无法施救了。原来CLR认为“内存永远也使用不完”也是有条件的啊!

 

第四节 垃圾回收过程托管堆中的一个对象,当线程中有变量对其引用则为可达对象,否则为不可达对象。

在一次垃圾回收过程开始时,垃圾回收器会认为堆中的所有对象都是垃圾。

第一步是标记对象,垃圾回收器沿着线程栈上行检查所有根,静态字段、方法参数、活动中的局部变量以及寄存器指向的对象等都是根,当发现有根引用了托管堆中的对象A时,垃圾回收器会对此对象A进行标记,在标记A时,如果检测到对象A内又引用了另一个对象B,则也对B进行标记,对一个根检测完毕后会接着检测下一个根,执行同样的标记过程,代码中很有可能多个对象中引用了同一个对象C,垃圾回收器只要检测到对象C已经被标记过,则不再对对象C内所引用的对象进行检测,以防止无限循环标记。有标记的对象就是可达对象,未标记的对象就是不可达对象。

第二步是搬迁对象压缩堆,垃圾回收器遍历堆中的所有对象来寻找未标记的对象,因为未标记的对象是垃圾对象,可以进行回收,如果发现对象较小,则忽略,否则会先释放这些垃圾对象所占的内存,再把可达对象搬迁到这里以压缩堆,在搬迁可达对象之后,所有指向这些对象的变量将无效,接着垃圾回收器要重新遍历应用程序的所有根来修改它们的引用。在这个过程中如果各个线程正在执行,很可能导致变量引用到无效的对象地址,所以整个进程的正在执行托管代码的线程是被挂起的。

其实在垃圾回收器准备开始一次回收时,正在执行托管代码的所有线程都必须被挂起,挂起时,CLR会记录每个线程的指令指针以确定线程当前执行到哪里以便将来在垃圾回收结束后进行恢复。如果一个线程的指令指针恰好到达了一个安全点,则可以挂起该线程,否则CLR会尝试劫持该线程,如果还未到达安全点,则等待几百毫秒后CLR会尝试再一次劫持该线程,有可能经过多次尝试,最终挂起该线程,当当前进程的所有执行托管代码的线程都挂起后,垃圾回收器就可以开始工作了。(有关线程劫持可查找相关资料)。垃圾回收器回收完毕后,CLR恢复所有线程,程序继续运行。可见,垃圾回收对性能影响之巨大!

 

第五节 大对象在创建新对象时,任何大于等于85000字节的对象都被认为是大对象,这些对象的内存是从大对象堆中分配的,大对象总是被认为是第2代对象,要尽量避免分配大对象来减少性能损伤,为了提高性能,垃圾回收器不对大对象进行搬迁压缩,只在回收第2代内存时进行回收。

 

第六节 手工进行回收一般的情况下,CLR会智能地在必要的时候更行垃圾回收,但我们也可以在我们愿意的情况下手动启动垃圾回收器,System.GC类提供了重载版本的静态方法来启动垃圾回收器:

//对所有代进行垃圾回收。
GC.Collect();
//对指定的代进行垃圾回收。
GC.Collect(int generation);
//强制在 System.GCCollectionMode 值所指定的时间对零代到指定代进行垃圾回收。
GC.Collect(int generation, GCCollectionMode mode); 

在上一节中我们已经知道,每一次垃圾回收过程都会导致性能损伤,所以我们尽量避免调用这3个方法进行垃圾回收,当然必要的时候也可以调用。

不仅仅以上谈到几种情况下会启动垃圾回收器,当CLR接到Windwos发出内存告急通知时也会启动垃圾回收、CLR卸载AppDomain时也会启动垃圾回收。

实例

 

 代码如下 复制代码

//File: MyClass.cs  
using System;  
using System.Collections.Generic;  
using System.Text;  
 
namespace ConsoleApplication2  
{  
    class MyClass  
    {  
        ~MyClass()  
        {  
            Console.WriteLine("In MyClass destructor+++++++++++++++++++++++++++");  
        }  
    }  
}//File: MyAnotherClass.cs  
using System;  
using System.Collections.Generic;  
using System.Text;  
 
namespace ConsoleApplication2  
{  
    public class MyAnotherClass  
    {  
        ~MyAnotherClass()  
        {  
            Console.WriteLine("In MyAnotherClass destructor___________________________________");  
        }  
    }  
}//File: Program.cs  
using System;  
using System.Collections.Generic;  
using System.Text;  
 
namespace ConsoleApplication2  
{  
    class Program  
    {  
        static void Main(string[] args)  
        {  
            MyClass myClass = new MyClass();  
            MyAnotherClass myAnotherClass = new MyAnotherClass();  
            WeakReference myShortWeakReferenceObject = new WeakReference(myClass);  
            WeakReference myLongWeakReferenceObject = new WeakReference(myAnotherClass, true);  
            Console.WriteLine("Release managed resources by setting locals to null.");  
            myClass = null;  
            myAnotherClass = null;  
 
            Console.WriteLine("Check whether the objects are still alive.");  
            CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");  
            CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");  
 
            Console.WriteLine("Programmatically cause GC.");  
            GC.Collect();  
 
            Console.WriteLine("Wait for GC runs the finalization methods.");  
            GC.WaitForPendingFinalizers();  
 
            //Check whether the objects are still alive.  
            CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");  
            CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");  
 
            Console.WriteLine("Programmatically cause GC again. Let's see what will happen this time.");  
            GC.Collect();  
 
            //Check whether the objects are still alive.  
            CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");  
            CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");  
 
            myAnotherClass = (MyAnotherClass)myLongWeakReferenceObject.Target;  
 
            Console.ReadLine();  
        }  
 
        static void CheckStatus(WeakReference weakObject, string strLocalVariableName, string strWeakObjectName)  
        {  
            Console.WriteLine(strLocalVariableName + (weakObject.IsAlive ? " is still alive." : " is not alive."));  
            Console.WriteLine(strWeakObjectName + (weakObject.Target != null ? ".Target is not null." : ".Target is null."));  
            Console.WriteLine();  
        }  
    }  

//File: MyClass.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication2
{
    class MyClass
    {
        ~MyClass()
        {
            Console.WriteLine("In MyClass destructor+++++++++++++++++++++++++++");
        }
    }
}//File: MyAnotherClass.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication2
{
    public class MyAnotherClass
    {
        ~MyAnotherClass()
        {
            Console.WriteLine("In MyAnotherClass destructor___________________________________");
        }
    }
}//File: Program.cs
using System;
using System.Collections.Generic;
using System.Text;

namespace ConsoleApplication2
{
    class Program
    {
        static void Main(string[] args)
        {
            MyClass myClass = new MyClass();
            MyAnotherClass myAnotherClass = new MyAnotherClass();
            WeakReference myShortWeakReferenceObject = new WeakReference(myClass);
            WeakReference myLongWeakReferenceObject = new WeakReference(myAnotherClass, true);
            Console.WriteLine("Release managed resources by setting locals to null.");
            myClass = null;
            myAnotherClass = null;

            Console.WriteLine("Check whether the objects are still alive.");
            CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");
            CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");

            Console.WriteLine("Programmatically cause GC.");
            GC.Collect();

            Console.WriteLine("Wait for GC runs the finalization methods.");
            GC.WaitForPendingFinalizers();

            //Check whether the objects are still alive.
            CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");
            CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");

            Console.WriteLine("Programmatically cause GC again. Let's see what will happen this time.");
            GC.Collect();

            //Check whether the objects are still alive.
            CheckStatus(myShortWeakReferenceObject, "myClass ", "myShortWeakReferenceObject");
            CheckStatus(myLongWeakReferenceObject, "myAnotherClass", "myLongWeakReferenceObject");

            myAnotherClass = (MyAnotherClass)myLongWeakReferenceObject.Target;

            Console.ReadLine();
        }

        static void CheckStatus(WeakReference weakObject, string strLocalVariableName, string strWeakObjectName)
        {
            Console.WriteLine(strLocalVariableName + (weakObject.IsAlive ? " is still alive." : " is not alive."));
            Console.WriteLine(strWeakObjectName + (weakObject.Target != null ? ".Target is not null." : ".Target is null."));
            Console.WriteLine();
        }
    }
}

 

垃圾回收机制要点整理

1. ASP.NET资源分托管资源和非托管资源,对于托管资源,.ASP.NET GC可以很好的回收无用的垃圾,而对于非托管(例如文件访问,网络访问等)需要手动清理垃圾(显式释放)。
2. 非托管资源的释放,ASP.NET提供了两种方式:
2-1.Finalizer:写法貌似C++的析构函数,本质上却相差甚远。Finalizer是对象被GC回收之前调用的终结器,初衷是在这里释放非托管资源,但由于GC运行时机的不确定性,通常会导致非托管资源释放不及时。另外,Finalizer可能还会有意想不到的副作用,比如:被回收的对象已经没有被其他可用对象所引用,但Finalizer内部却把它重新变成可用,这就破坏了GC垃圾收集过程的原子性,增大了GC开销。
2-2.Dispose模式:C#提供using关键字支持Dispose Pattern进行资源释放。这样能通过确定的方式释放非托管资源,而且using结构提供了异常安全性。所以,一般建议采用Dispose Pattern,并在Finalizer中辅以检查,如果忘记显式Dispose对象则在Finalizer中释放资源。
3. 托管资源的回收,判断对象是否要被回收只要判定此对象或者其包含的子对象没有任何引用是有效的
4. GC的代价:一则丧失了托管资源回收的实时性,二是没有把C#托管资源和非托管资源的管理统一起来,造成概念割裂
5. ASP.NET类型分两大类:引用类型、值类型,值类型分配在栈上,不需要GC回收;引用类型分配在堆上,它的释放和回收需要GC来完成。一个引用类型的对象要被回收,需要要成为垃圾
6. 系统为GC安排了独立线程,对于内存回收GC采取了一定的优先算法进行轮循回收内存资源
7. Generation(代),为了提高性能,越老的对象存活的越久。ASP.NET中一般分为三代,G0,G1,G2;G0最先被回收。

 

何时回收?

垃圾收集器周期性的执行内存清理工作,一般在以下情况出现时垃圾收集器将会启动:

(1)内存不足溢出时,更确切地应该说是第0代对象充满时。

(2)调用GC.Collect方法强制执行垃圾回收。

(3)Windows报告内存不足时,CLR将强制执行垃圾回收。

(4)CLR卸载AppDomain时,GC将对所有代龄的对象执行垃圾回收。

(5)其他情况,例如物理内存不足,超出短期存活代的内存段门限,运行主机拒绝分配内存等等。

时间: 2024-10-27 13:08:22

asp.net C#基础知识之垃圾回收机制介绍的相关文章

垃圾回收机制及析构器原理解析

前言 当学习到Web API中摸索原理时,对于其中有关垃圾回收只是有点印象并未深入去了解其原理并且对索引器用的也很少,所以利用放假期间好好回顾下已经忘记或者遗漏的知识,温故而知新大概就是这道理吧,虽然园子中关于这两者的文章也是多不胜数,但笔者也有自己独特的见解. 垃圾回收机制 引言 我们知道.NET Framework中的对象是创建在托管堆中的,但是像C.C++等其他底层语言中的对象是创建在非托管堆中的,所以在这类语言中就会出现编程人员忘记去释放已经没有用的对象,同时编程人员也可能会去试图访问已

析JAVA之垃圾回收机制

本文为2010年编写,所以有很多看法不是很准确,有一定的参考价值,如需要更加深入细节,请参看,2012年编写的关于JVM的文章: 认识JVM--第一篇-对象生成&回收算法 认识JVM--第二篇-java对象内存模型 JVM第三篇(简单demo) 系统架构-性能篇章1(应用系统性能2-OOM&参数配置) 相继的还会有更多的java深入的知识和机制. 对于JAVA编程和很多类似C.C++语言有一个巨大区别就是内存不需要自己去free或者delete,而是由JVM垃圾回收机制去完成的.对于这个过

Android垃圾回收机制及程序优化System.gc_Android

1.垃圾收集算法的核心思想 Java语言建立了垃圾收集机制,用以跟踪正在使用的对象和发现并回收不再使用(引用)的对象.该机制可以有效防范动态内存分配中可能发生的两个危险:因内存垃圾过多而引发的内存耗尽,以及不恰当的内存释放所造成的内存非法引用. 垃圾收集算法的核心思想是:对虚拟机可用内存空间,即堆空间中的对象进行识别,如果对象正在被引用,那么称其为存活对象,反之,如果对象不再被引用,则为垃圾对象,可以回收其占据的空间,用于再分配.垃圾收集算法的选择和垃圾收集系统参数的合理调节直接影响着系统性能,

.Net 垃圾回收机制原理(一)

英文原文:Jeffrey Richter 编译:赵玉开 链接:http://www.cnblogs.com/yukaizhao/archive/2011/11/23/dot_net_GC_1.html 有了Microsoft.Net clr中的垃圾回收机制程序员不需要再关注什么时候释放内存,释放内存这件事儿完全由GC做了,对程序员来说是透明的.尽管如此,作为一个.Net程序员很有必要理解垃圾回收是如何工作的.这篇文章我们就来看下.Net是如何分配和管理托管内存的,之后再一步一步描述垃圾回收器工作

Python垃圾回收机制总结

Python 垃圾回收机制 内存管理 Python中的内存管理机制的层次结构提供了4层,其中最底层则是C运行的malloc和free接口,往上的三层才是由Python实现并且维护的,第一层则是在第0层的基础之上对其提供的接口进行了统一的封装,因为每个系统都可能差异性.   内存池 Python为了避免频繁的申请和删除内存所造成系统切换于用户态和核心态的性能问题,从而引入了内存池机制,专门用来管理小内存的申请和释放.内存池分为四层:block.pool.arena和内存池.如下图: block:有

详解C#中的定时器Timer类及其垃圾回收机制_C#教程

关于C# Timer类  在C#里关于定时器类就有3个 C# Timer使用的方法1.定义在System.Windows.Forms里 C# Timer使用的方法2.定义在System.Threading.Timer类里  " C# Timer使用的方法3.定义在System.Timers.Timer类里 下面我们来具体看看这3种C# Timer用法的解释: (1)System.Windows.Forms.Timer 应用于WinForm中的,它是通过Windows消息机制实现的,类似于VB或D

PHP垃圾回收机制详解

PHP的基本GC概念 PHP语言同其他语言一样,具有垃圾回收机制.那么今天我们要为大家讲解的内容就是关于PHP垃圾回收机制的相关问题.希望对大家有所帮助. PHP strtotime应用经验之谈PHP memory_get_usage()管理内存PHP unset全局变量运用问题详解PHP unset()函数销毁变量教你快速实现PHP全站权限验证一.PHP 垃圾回收机制(Garbage Collector 简称GC) 在PHP中,没有任何变量指向这个对象时,这个对象就成为垃圾.PHP会将其在内存

关于js垃圾回收机制的问题

问题描述 关于js垃圾回收机制的问题 var i; for(i=0;i<length;i++){ } for(var i=0;i<length;i++){ } 两个基础问题,我从外定义和在for循环定义i的区别在哪里 众所周知js没有块级作用域,那么我定义的i 在垃圾回收机制中算不算进入环境和离开环境呢? 解决方案 你2个是等级的,主要看for语句体是否有闭包引用到i,引用到是不会释放的 解决方案二: 也有类似对象操作的吧.在一个函数里返回的对象与其他的对象互不干扰.

细述 Java垃圾回收机制→Java Garbage Collection Introduction

计划写一个介绍Java垃圾回收基础的系列文章,共分四部分: Java垃圾回收简介 Java垃圾回收器是如何工作的? 各种类型的Java垃圾回收器 Java垃圾回收的监控和分析 本文是这个系列的第一篇文章,这篇文章将会介绍一些基本术语,如:JDK,JVM,JRE,HotSpot VM,以及理解JVM的架构和Java堆内存结构.在开始学习Java垃圾回收机制之前确实有必要了解一下这些基本东西. 关键的Java术语 Java API–一个帮助程序员创建Java应用的打包好的库集合 Java Devel