`
北极的。鱼
  • 浏览: 158941 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

[转]net GC

 
阅读更多

转自:http://blog.csdn.net/sykpboy/article/details/342971

http://www.cnblogs.com/lzh/archive/2007/07/18/822388.html

http://blog.sina.com.cn/s/blog_740c17ab0100p2gw.html

 

便于对文章的开展,需要先明确两个概念。
第一个就是很多人用.Net写程序,会谈到托管这个概念。那么.Net所指的资源托管到底是什么意思,是相对于所有资源,还是只限于某一方面资源?很多人对此不是很了解,其实.Net所指的托管只是针对内存这一个方面,并不是对于所有的资源;因此对于Stream,数据库的连接,GDI+的相关对象,还有Com对象等等,这些资源并不是受到.Net管理而统称为非托管资源。而对于内存的释放和回收,系统提供了GC-Garbage Collector,而至于其他资源则需要手动进行释放。

那么第二个概念就是什么是垃圾,通过我以前的文章,会了解到.Net类型分为两大类,一个就是值类型,另一个就是引用类型。前者是分配在栈上,并不需要GC回收;后者是分配在堆上,因此它的内存释放和回收需要通过GC来完成。GC的全称为“Garbage Collector,顾名思义就是垃圾回收器,那么只有被称为垃圾的对象才能被GC回收。也就是说,一个引用类型对象所占用的内存需要被GC回收,需要先成为垃圾。那么.Net如何判定一个引用类型对象是垃圾呢,.Net的判断很简单,只要判定此对象或者其包含的子对象没有任何引用是有效的,那么系统就认为它是垃圾。

明确了这两个基本概念,接下来说说GC的运作方式以及其的功能。内存的释放和回收需要伴随着程序的运行,因此系统为GC安排了独立的线程。那么GC的工作大致是,查询内存中对象是否成为垃圾,然后对垃圾进行释放和回收。那么对于GC对于内存回收采取了一定的优先算法进行轮循回收内存资源。其次,对于内存中的垃圾分为两种,一种是需要调用对象的析构函数,另一种是不需要调用的。GC对于前者的回收需要通过两步完成,第一步是调用对象的析构函数,第二步是回收内存,但是要注意这两步不是在GC一次轮循完成,即需要两次轮循;相对于后者,则只是回收内存而已。

很明显得知,对于某个具体的资源,无法确切知道,对象析构函数什么时候被调用,以及GC什么时候会去释放和回收它所占用的内存。那么对于从CC++之类语言转换过来的程序员来说,这里需要转变观念。

那么对于程序资源来说,我们应该做些什么,以及如何去做,才能使程序效率最高,同时占用资源能尽快的释放。前面也说了,资源分为两种,托管的内存资源,这是不需要我们操心的,系统已经为我们进行管理了;那么对于非托管的资源,这里再重申一下,就是Stream,数据库的连接,GDI+的相关对象,还有Com对象等等这些资源,需要我们手动去释放。

如何去释放,应该把这些操作放到哪里比较好呢。.Net提供了三种方法,也是最常见的三种,大致如下:
<!--[if !supportLists]-->1.  <!--[endif]-->析构函数;
<!--[if !supportLists]-->2.  <!--[endif]-->继承IDisposable接口,实现Dispose方法;
<!--[if !supportLists]-->3.  <!--[endif]-->提供Close方法。

经过前面的介绍,可以知道析构函数只能被GC来调用的,那么无法确定它什么时候被调用,因此用它作为资源的释放并不是很合理,因为资源释放不及时;但是为了防止资源泄漏,毕竟它会被GC调用,因此析构函数可以作为一个补救方法。而CloseDispose这两种方法的区别在于,调用完了对象的Close方法后,此对象有可能被重新进行使用;而Dispose方法来说,此对象所占有的资源需要被标记为无用了,也就是此对象被销毁了,不能再被使用。例如,常见SqlConnection这个类,当调用完Close方法后,可以通过Open重新打开数据库连接,当彻底不用这个对象了就可以调用Dispose方法来标记此对象无用,等待GC回收。明白了这两种方法的意思后,大家在往自己的类中添加的接口时候,不要歪曲了这两者意思。

接下来说说这三个函数的调用时机,我用几个试验结果来进行说明,可能会使大家的印象更深。
首先是这三种方法的实现,大致如下:

 

///<summary>
/// The class to show three disposal function
///</summary>
    public class DisposeClass:IDisposable
    {
        public void Close()
        {
            Debug.WriteLine( "Close called!" );
        }


        ~DisposeClass()
        {
            Debug.WriteLine( "Destructor called!" );
        }

        #region IDisposable Members
        public void Dispose()
        {
            // TODO:  Add DisposeClass.Dispose implementation
            Debug.WriteLine( "Dispose called!" );
        }
        #endregion
    }

对于Close来说不属于真正意义上的释放,除了注意它需要显示被调用外,我在此对它不多说了。而对于析构函数而言,不是在对象离开作用域后立刻被执行,只有在关闭进程或者调用GC.Collect方法的时候才被调用,参看如下的代码运行结果。

 

 

 

private void Create()
        {
            DisposeClass myClass = new DisposeClass();
        }

        private void CallGC()
        {
            GC.Collect();
        }

        // Show destructor
        Create();
        Debug.WriteLine( "After created!" );
        CallGC();

运行的结果为:

 

After created!

Destructor called!

显然在出了Create函数外,myClass对象的析构函数没有被立刻调用,而是等显示调用GC.Collect才被调用。

对于Dispose来说,也需要显示的调用,但是对于继承了IDisposable的类型对象可以使用using这个关键字,这样对象的Dispose方法在出了using范围后会被自动调用。例如:

    using( DisposeClass myClass = new DisposeClass() )

    {

        //other operation here

    }


如上运行的结果如下:
Dispose called!

那么对于如上DisposeClass类型的Dispose实现来说,事实上GC还需要调用对象的析构函数,按照前面的GC流程来说,GC对于需要调用析构函数的对象来说,至少经过两个步骤,即首先调用对象的析构函数,其次回收内存。也就是说,按照上面所写的Dispose函数,虽说被执行了,但是GC还是需要执行析构函数,那么一个完整的Dispose函数,应该通过调用GC.SuppressFinalize(this )来告诉GC,让它不用再调用对象的析构函数中。那么改写后的DisposeClass如下:

 

///<summary>
/// The class to show three disposal function
///</summary>
    public class DisposeClass:IDisposable
    {
        public void Close()
        {
            Debug.WriteLine( "Close called!" );
        }

        ~DisposeClass()
        {
            Debug.WriteLine( "Destructor called!" );
        }

        #region IDisposable Members
        public void Dispose()
        {
            // TODO:  Add DisposeClass.Dispose implementation
            Debug.WriteLine( "Dispose called!" );
            GC.SuppressFinalize( this );
        }
        #endregion
    }

通过如下的代码进行测试。

 

 

private void Run()
        {
            using( DisposeClass myClass = new DisposeClass() )
            {
                //other operation here
            }
        }

        private void CallGC()
        {
            GC.Collect();
        }

        // Show destructor
        Run();
        Debug.WriteLine( "After Run!" );
        CallGC();

运行的结果如下:

 

Dispose called!

After Run!


显然对象的析构函数没有被调用。通过如上的实验以及文字说明,大家会得到如下的一个对比表格。 

 

 

 

 

======================================================================

 

 

 

GC工作方式
 

 

首先我们要知道托管代码中的对象什么时候回收我们管不了(除非用GC.Collect强迫GC回收,这不推荐,后面会说明为什么)。GC会在它"高兴"的时候执行一次回收(这有许多原因,比如内存不够用时。这样做是为了提高内存分配、回收的效率)。那么如果我们用Destructor呢?同样不行,因为.NET中Destructor的概念已经不存在了,它变成了Finalizer,这会在后面讲到。目前请记住一个对象只有在没有任何引用的情况下才能够被回收。为了说明这一点请看下面这一段代码:
object objA = new object();
object objB = objA;
objA = null;
// 强迫回收。
GC.Collect();
objB.ToString(); 
 这里objA引用的对象并没有被回收,因为这个对象还有另一个引用,ObjB。
对象在没有任何引用后就有条件被回收了。当GC回收时,它会做以下几步:
确定对象没有任何引用。
检查对象是否在Finalizer表上有记录。
如果在Finalizer表上有记录,那么将记录移到另外的一张表上,在这里我们叫它Finalizer2。
如果不在Finalizer2表上有记录,那么释放内存。
在Finalizer2表上的对象的Finalizer会在另外一个low priority的线程上执行后从表上删除。当对象被创建时GC会检查对象是否有Finalizer,如果有就会在Finalizer表中添加纪录。我们这里所说的记录其实就是指针。如果仔细看这几个步骤,我们就会发现有Finalizer的对象第一次不会被回收,也就是,有Finalizer的对象要一次以上的Collect操作才会被回收,这样就要慢一步,所以作者推荐除非是绝对需要不要创建Finalizer。为了证明GC确实这么工作而不是作者胡说,我们将在对象的复活一章中给出一个示例,眼见为实,耳听为虚嘛!^_^
GC为了提高回收的效率使用了Generation的概念,原理是这样的,第一次回收之前创建的对象属于Generation 0,之后,每次回收时这个Generation的号码就会向后挪一,也就是说,第二次回收时原来的Generation 0变成了Generation 1,而在第一次回收后和第二次回收前创建的对象将属于Generation 0。GC会先试着在属于Generation 0的对象中回收,因为这些是最新的,所以最有可能会被回收,比如一些函数中的局部变量在退出函数时就没有引用了(可被回收)。如果在Generation 0中回收了足够的内存,那么GC就不会再接着回收了,如果回收的还不够,那么GC就试着在Generation 1中回收,如果还不够就在Generation 2中回收,以此类推。Generation也有个最大限制,根据Framework版本而定,可以用GC.MaxGeneration获得。在回收了内存之后GC会重新排整内存,让数据间没有空格,这样是因为CLR顺序分配内存,所以内存之间不能有空着的内存。现在我们知道每次回收时都会浪费一定的CPU时间,这就是我说的一般不要手动GC.Collect的原因(除非你也像我一样,写一些有关GC的示例!^_^)。
 
Destructor的没落,Finalizer的诞生 
对于Visual Basic程序员来说这是个新概念,所以前一部分讲述将着重对C++程序员。我们知道在C++中当对象被删除时(delete),Destructor中的代码会马上执行来做一些内存释放工作(或其他)。不过在.NET中由于GC的特殊工作方式,Destructor并不实际存在,事实上,当我们用Destructor的语法时,编译器会自动将它写为protected virtual void Finalize(),这个方法就是我所说的Finalizer。就象它的名字所说,它用来结束某些事物,不是用来摧毁(Destruct)事物。在Visual Basic中它就是以Finalize方法的形式出现的,所以Visual Basic程序员就不用操心了。C#程序员得用Destructor的语法写Finalizer,不过千万不要弄混了,.NET中已经没有Destructor了。C++中我们可以准确的知道什么时候会执行Destructor,不过在.NET中我们不能知道什么时候会执行Finalizer,因为它是在第一次对象回收操作后才执行的。我们也不能知道Finalizer的执行顺序,也就是说同样的情况下,A的Finalize可能先被执行,B的后执行,也可能A的后执行而B的先执行。也就是说,在Finalizer中我们的代码不能有任何的时间逻辑。下面我们以计算一个类有多少个实例为示例,指出Finalizer与Destructor的不同并指出在Finalizer中有时间逻辑的错误,因为Visual Basic中没有过Destructor所以示例只有C#版:
public class CountObject 
{ 
public static int Count = 0; 
public CountObject() { Count++; } 
~CountObject() { Count--; } 
} 
static void Main() { 
CountObject obj; 
for (int i = 0; i < 5; i++) 
{ 
obj = null; // 这一步多余,这么写只是为了更清晰些! 
obj = new CountObject(); 
} // Count不会是1,因为Finalizer不会马上被触发,要等到有一次回收操作后才会被触发。 
Console.WriteLine(CountObject.Count); 
Console.ReadLine(); 
}
注意以上代码要是改用C++写的话会发生内存泄漏,因为我们没有用delete操作符手动清理内存,但是在托管代码中却不会发生内存泄漏,因为GC会自动检测没有引用了的对象并回收。这里作者推荐你只在实现IDisposable接口时配合使用Finalizer,在其他的情况下不要使用(可能会有特殊情况)。在非托管资源的释放一章我们会更好的了解IDisposable接口,现在让我们来做耶稣吧!
 
 
对象的复活 
什么?回收的对象也可以"复活"吗?没错,虽然这么说的定义不准确。让我们先来看一段代码:
public class Resurrection 
{ 
public int Data; 
public Resurrection(int data) { this.Data = data; } 
~Resurrection() { Main.Instance = this; } } 
public class Main { 
public static Resurrection Instance; 
public static void Main() 
{ 
Instance = new Resurrection(1); 
Instance = null; 
GC.Collect(); 
GC.WaitForPendingFinalizers(); // 看到了吗,在这里“复活”了。 
Console.WriteLine(Instance.Data); Instance = null; GC.Collect(); 
Console.ReadLine(); 
} 
}
 你可能会问:"既然这个对象能复活,那么这个对象在程序结束后会被回收吗?"。会,"为什么?"。让我们按照GC的工作方式走一遍你就明白是怎么回事了。 1、执行Collect。检查引用。没问题,对象已经没有引用了。 
2、创建新实例时已经在Finalizer表上作了纪录,所以我们检查到了对象有Finalizer。 3、因为查到了Finalizer,所以将记录移到Finalizer2表上。 4、在Finalizer2表上有记录,所以不释放内存。 
5、Collect执行完毕。这时我们用了GC.WaitForPendingFinalizers,所以我们将等待所有Finalizer2表上的Finalizers的执行。 
6、Finalizer执行后我们的Instance就又引用了我们的对象。(复活了) 7、再一次去除所有的引用。 
8、执行Collect。检查引用。没问题。 
9、由于上次已经将记录从Finalizer表删除,所以这次没有查到对象有Finalizer。 10、在Finalizer2表上也不存在,所以对象的内存被释放了。 
现在你明白原因了,让我来告诉你"复活"的用处。嗯,这个„„好吧,我不知道。其实,复活没有什么用处,而且这样做也非常的危险。看来这只能说是GC机制的漏洞(请参看GC.ReRegisterForFinalize再动脑筋想一下就知道为什么可以说是漏洞了)。作者建议大家忘掉有什么复活,避免这类的使用。可能你会问:"那你干吗还要对我们说这些?"我说这些为的是让大家更好的了解GC的工作机制!^_^  
--------------------------------------------------------------------------------  
非托管资源的释放 到现在为止,我们说了托管内存的管理,那么当我们利用如数据库、文件等非托管资源时呢?这时我们就要用到.NET Framework中的标准:IDisposable接口。按照标准,所有有需要手动释放非托管资源的类都得实现此接口。这个接口只有一个方法,Dispose(),不过有相对的Guidelines指示如何实现此接口,在这里我向大家说一说。实现IDisposable这个接口的类需要有这样的结构:
public class Base : IDisposable 
{ 
public void Dispose() { this.Dispose(true); 
GC.SupressFinalize(this); 
} 

protected virtual void Dispose(bool disposing) 
{ 
if (disposing) { // 托管类 } 
// 非托管资源释放 
} 

~Base() 
{ 
this.Dispose(false); 
} 
} 
public class Derive : Base 
{ 
protected override void Dispose(bool disposing) 
{ if (disposing) 
{ 
// 托管类 
} 
// 非托管资源释放 base.Dispose(disposing); 
} 
}
 为什么要这样设计呢?让我在后面解说一下。现在我们讲讲实现这个Dispose方法的几个准则: 
它不能扔出任何错误,重复的调用也不能扔出错误。也就是说,如果我已经调用了一个对象的Dispose,当我第二次调用Dispose的时候程序不应该出错,简单地说程序在第二次调用Dispose时不会做任何事。这些可以通过一个flag或多重if判断实现。  
一个对象的Dispose要做到释放这个对象的所有资源。拿一个继承类为例,继承类中用到了非托管资源所以它实现了IDisposable接口,如果继承类的基类也用到了非托管资源那么基类也得被释放,基类的资源如何在继承类中释放呢?当然是通过一个virtual/Overridable方法了,这样我们能保证每个Dispose都被调用到。这就是为什么我们的设计有一个virtual/Overridable的Dispose方法。注意我们首先要释放继承类的资源然后再释放基类的资源。  
因为非托管资源一定要被保障正确释放所以我们要定义一个Finalizer来避免程序员忘了调用Dispose的情况。上面的设计就采用了这种形式。如果我们手动调用Dispose方法就没有必要再保留Finalizer了,所以在Dispose中我们用了GC.SupressFinalize将对象从Finalizer表去掉,这样再回收时速度会更快。  
那么那个disposing和"托管类"是怎么回事呢?是这样:在"托管类"中写所有你想在调用Dispose时让其处于可释放状态的托管代码。还记得我们说过我们不知道托管代码是什么时候释放的吗?在这里我们只是去掉成员对象的引用让它处于可被回收状态,并不是直接释放内存。在"托管类"中这里我们也要写上所有实现了IDisposable的成员对象,因为他们也有Dispose,所以也需要在对象的Dispose中调用他们的Dispose,这样才能保证第二个准则。disposing是为了区分Dispose的调用方法,如果我们手动调用那么为了第二个准则"托管类"部分当然得执行,但如果是Finalizer调用的Dispose,这时候对象已经没有任何引用,也就是说对象的成员自然也就不存在了(无引用),也就没有必要执行"托管类"部分了,因为他们已经处于可被回收状态了。好了,这就是IDisposable接口的全部了。现在让我们来回想一下,以前我们可能认为有了Dispose内存就会马上被释放,这是错误的。只有非托管内存才会被马上释放,托管内存的释放由GC管理,我们不用管。  
--------------------------------------------------------------------------------  
弱引用的使用 
A = B,我们称这样的引用叫做强引用,GC就是通过检查强引用来决定一个对象是否是可以回收的。另外还有一种引用称作弱引用(WeakReference),这种引用不影响GC回收,这就是它的用处所在。你会问到底有什么用处。现在我们来假设我们有一个很胖的对象,也就是说它占用很多内存。我们用过了这个对象,打算将它的引用去掉好让GC可以回收内存,但是功夫不大我们又需要这个对象了,没办法,重新创建实例,怎么创建这么慢啊?有什么办法解决这样的问题?有,将对象留在内存中不就快了嘛!不过我们不想这样胖得对象总占着内存,而我们也不想总是创建这样胖的新实例,因为这样很耗时。那怎么办„„?聪明的朋友一定已经猜到了我要说解决方法是弱引用。不错,就是它。我们可以创建一个这个胖对象的弱引用,这样在内存不够时GC可以回收,不影响内存使用,而在没有被GC回收前我们还可以再次利用该对象。这里有一个示例:
public class Fat 
{ 
public int Data; 
public Fat(int data) { this.Data = data; } 
} 
public class Main 
{ 
public static void Main() 
{ 
Fat oFat = new Fat(1); 
WeakReference oFatRef = new WeakReference(oFat); // 从这里开始,Fat对象可以被回收了。 
oFat = null; 
if (oFatRef.IsAlive) 
{ 
Console.WriteLine(((Fat) oFatRef.Target).Data); // 1 
} // 强制回收。 
GC.Collect(); 
Console.WriteLine(oFatRef.IsAlive); // False Console.ReadLine(); 
} 
}
这里我们的Fat其实并不是很胖,但是可以体现示例的本意:如何使用弱引用。那如果Fat有Finalizer呢,会怎样?如果Fat有Finalizer那么我们可能会用到WeakReference的另一个构造函数,当中有一参数叫做TrackResurrection,如果是True,只要Fat的内存没被释放我们就可以用它,也就是说Fat的Finalizer执行后我们还是可以恢复Fat(相当于第一次回收操作后还可恢复Fat);如果TrackResurrection是False,那么第一次回收操作后就不能恢复Fat对象了。  
--------------------------------------------------------------------------------  
总结 
我在这里写出了正篇文章的要点: 
一个对象只当在没有任何引用的情况下才会被回收。  
一个对象的内存不是马上释放的,GC会在任何时候将其回收。  一般情况下不要强制回收工作。  
如果没有特殊的需要不要写Finalizer。  
不要在Finalizer中写一些有时间逻辑的代码。  
在任何有非托管资源或含有Dispose的成员的类中实现IDisposable接口。  按照给出的Dispose设计写自己的Dispose代码。  当用胖对象时可以考虑弱引用的使用。  
好了,就说到这里了,希望对GC的了解会让您的代码更加稳固,更加简洁,更加快!更重要的,不再会有内存管理问题,无论是托管还是非托管!  
--------------------------------------------------------------------------------  
参考信息 1、GC Class 
ms-help://MS.VSCC/MS.MSDNVS.2052/cpref/html/frlrfSystemGCClassTopic.htm 2、Programming For Garbage Collection 
ms-help://MS.VSCC/MS.MSDNVS.2052/cpguide/html/cpconprogrammingessentialsforgarbagecollection.htm 
3、Curso de Erik, Septima Entrega (西班牙语) 
http://guille.costasol.net/NET/cursoCSharpErik/Entrega7/Entrega7.htm
分享到:
评论
5 楼 北极的。鱼 2014-12-29  
针对非托管对象调用finalize和dispose(dispose当然也处理托管对象)方法后,只是告诉CLR这个对象已经是垃圾了,可以被处理。但是垃圾对象具体什么时候被处理,需要等待GC的决定。
不仅是非托管对象如上所述,就算是托管对象被标记为垃圾对象后,也不是立即从内存中消失,也要等待GC的清理才能消失。
在GC.SuppressFinalize(this);之后不会立即执行清理,这也只是标记这个对象为垃圾对象。GC的清理操作发生在某个代中可用空间不足时才会执行垃圾对象的压缩和清理。但是,你也可以调用GC.Collect()方法去回收。不过GC.Collect是很耗时的,它要整理现在程序所使用的内存,然后有些数据还要进行内存移动,同时对CPU的占用很大(查看一个程序的性能,要综合考虑CPU,内存,IO,网络)
4 楼 北极的。鱼 2014-12-28  
----------------------------------以下是CSDN上一位高手的总结----------------------------------------------

1、Finalize方法(C#中是析构函数,以下称析构函数)是用于释放非托管资源的,而托管资源会由GC自动回收。所以,我们也可以这样来区分 托管和非托管资源。所有会由GC自动回收的资源,就是托管的资源,而不能由GC自动回收的资源,就是非托管资源。在我们的类中直接使用非托管资源的情况很 少,所以基本上不用我们写析构函数。

2、大部分的非托管资源会给系统带来很多负面影响,例如数据库连接不被释放就可能导致连接池中的可用数据库连接用尽。文件不关闭会导致其它进程无法读写这个文件等等。 (数据库连接,文件读写,窗体程序等都是非托管的。如果不及时处理,会带来很多性能负担)

实现模型:
1、由于大多数的非托管资源都要求可以手动释放方式,而不能完全依赖finalize,性能上和用户需求上考虑也应该如此(非托管资源比较宝贵,需要及时释放占用)。所以,我们应该专门为释放非托管资源公开一个方法。实现IDispose接口的Dispose方法是最好的模型,因为C#支持using语句快,可以在离开语句块时自动调用Dispose方法

2、虽然可以手动释放非托管资源,我们仍然要在析构函数中释放非托管资源,这样才是安全的应用程序。否则如果因为程序员的疏忽忘记了手动释放非托管资源, 那么就会带来灾难性的后果。所以说在析构函数中释放非托管资源,是一种补救的措施,至少对于大多数类来说是如此。

3、由于析构函数的调用将导致GC对对象回收的效率降低,所以如果已经完成了析构函数该干的事情(例如释放非托管资源),就应当使用SuppressFinalize方法告诉GC不需要再执行某个对象的析构函数。

4、析构函数中只能释放非托管资源而不能对任何托管的对象/资源进行操作。因为你无法预测析构函数的运行时机,所以,当析构函数被执行的时候,也许你进行操作的托管资源已经被释放了。这样将导致严重的后果。

5、(这是一个规则)如果一个类拥有一个实现了IDispose接口类型的成员,并创建(注意是创建,而不是接收,必须是由类自己创建)它的实例对象,则 这个类也应该实现IDispose接口,并在Dispose方法中调用所有实现了IDispose接口的成员的Dispose方法。
只有这样的才能保证所有实现了IDispose接口的类的对象的Dispose方法能够被调用到,确保可以手动释放任何需要释放的资源。
3 楼 北极的。鱼 2014-12-28  
Dispose()函数是被其它代码(程序员自己手动,或者用using语句块)显式调用并要求释放资源的,而析构函数是由系统(GC)调用的,原因如下:
1,托管对象还有用:在GC调用的时候Finalizer所引用的其它托管对象可能还不需要被销毁,并且即使要销毁,也会由GC来调用。
2,托管对象已经被销毁:Finalize一般被调用的时间顺序都很后,因为一般都要经过2次以上的GC才能真正被调用。换言之可能在析构函数调用之前,类包含的托管资源已经被回收了,如果Finanlize再清理托管资源,会导致无法预知的结果。
因此在Finalize中只需要释放非托管资源即可。
另外一方面,由于在 Dispose()中已经释放了托管和非托管的资源,因此在对象被GC回收时再次调用Finalize是没有必要的,所以在Dispose()中调用 GC.SuppressFinalize(this)避免重复调用Finalize。
2 楼 北极的。鱼 2014-12-28  
在.NET的对象中实际上有两个用于释放资源的函数:Dispose和Finalize。Finalize的目的是用于释放非托管的资源,而Dispose是用于释放所有资源,包括托管的和非托管的。
1 楼 北极的。鱼 2014-12-28  
注意,不能在析构函数中释放托管资源,因为析构函数是有垃圾回收器调用的,可能在析构函数调用之前,类包含的托管资源已经被回收了,从而导致无法预知的结果。

相关推荐

    GC5025.tar.gz_RK camera_camera_gc5025 RK3368_gc5025驱动_rk摄像头驱动

    《GC5025.tar.gz:RK3368平台上的GC5025摄像头驱动详解》 在嵌入式系统开发中,摄像头驱动扮演着至关重要的角色,它连接硬件设备与软件应用,使得图像捕获和处理成为可能。本文将深入探讨针对RK3368平台的GC5025...

    gc2355_mipi_raw_摄像头gc2355驱动_

    【标题】"gc2355_mipi_raw_摄像头gc2355驱动_" 指的是一个针对特定型号摄像头GC2355的驱动程序,该驱动是为MT6737处理器平台设计的。在嵌入式系统中,摄像头驱动是连接硬件设备(即摄像头)与操作系统内核之间的重要...

    图纸转换工具GC2000是一款超强悍的GERBER文件查看软件

    图纸转换工具GC2000是一款专为PCB布局工程师设计的强大GERBER文件查看软件,其功能强大,操作简便,是电子设计领域不可或缺的辅助工具。GC2000不仅能够帮助用户高效地查看和理解复杂的电路板设计,还支持多种格式的...

    GC0329 datasheet

    ### GC0329 CMOS 图像传感器技术详解 #### 一、传感器概述 ##### 1.1 一般描述 GC0329是一款由国内最大的CMOS图像传感器制造商格科微电子研发的新一代VGA图像传感器。该传感器拥有640×480像素的分辨率,采用1/9...

    用GC转换CAD档学习教案.pptx

    这份教程主要讲解了如何使用GC软件来转换和对齐CAD文件与Gerber文件,以便于在电路板设计中确保精确的布局。以下是详细步骤及相关的知识点: 1. **导入Gerber文件** - 在GC软件中,通过菜单栏选择`File` &gt; `Import...

    gc0339datasheet

    根据给定的文件信息,以下是关于GC0339图像传感器的数据手册中提到的技术知识点详细说明: 标题解析:“gc0339datasheet”指的是GC0339图像传感器的数据手册。数据手册是详细记录产品规格、技术参数、引脚描述、...

    GC4663 DATASHEET

    根据提供的文档内容,我们可以梳理出关于GC4663数据手册的相关知识点如下: 1. 传感器概述: 1.1 通用描述: GC4663是一款高质量的400万像素CMOS图像传感器,适用于安全摄像头、数码相机产品以及手机摄像头应用。该...

    摄像头GC0328

    GC0328是一款由Galaxy Core公司生产的1/6.5英寸VGACMOS图像传感器,具有高分辨率和良好的成像性能,特别适用于移动设备和电子产品的摄像头应用。 传感器概述: GC0328拥有640x480像素的分辨率和1/6.5英寸的光学格式...

    GC-GERBER转CAD坐标.-smt

    GC-GERBER转CAD坐标.-smt 离线编程可以学习哦

    1.28寸的GC9a01屏幕资料

    标题中的“1.28寸的GC9a01屏幕资料”指的是一个1.28英寸大小的显示屏,型号为GC9a01。这种屏幕通常用于小型电子设备,如智能手表、便携式仪器或物联网设备的显示界面。GC9a01是一款基于SPI(Serial Peripheral ...

    GC-PowerStation5.44.rar

    《GC-PowerStation5.44:SMT生产的GERBER与坐标转换利器详解》 在电子制造领域,尤其是表面贴装技术(Surface Mount Technology,简称SMT)中,精确的元器件布局和焊接至关重要。GC-PowerStation5.44是一款专业级的...

    GC9503V-DS IC规格书

    《GC9503V-DS IC规格书》是一份重要的技术文档,主要面向嵌入式开发领域的工程师,尤其在进行点屏操作或编写显示驱动程序时,此规格书是不可或缺的参考资料。嵌入式系统通常涉及到硬件和软件的紧密集成,其中显示...

    3516EV300+GC2053.zip

    标题中的"3516EV300+GC2053.zip"表明这是一个与HI3516EV300芯片和GC2053传感器相关的软件或驱动程序包。描述中提到"GC2053用于HI3518EV300HI3516EV200//HI3516EV300/ LITEOS 的驱动",这暗示GC2053是针对这三个不同...

    GC2093 CSP Datasheet Beta0.3 .pdf

    **GC2093 CSP Datasheet Beta0.3 .pdf** 文件主要介绍了一款由格科微(Galaxycore)公司推出的高动态范围(HDR)1080P CMOS图像传感器——GC2093。这款传感器设计用于安全摄像头、数码相机以及手机摄像头应用。以下...

    ESP32驱动GC9A01圆形屏幕显示图片并按照设定的时间自动更换.zip

    GC9A01则是一款专用于LCD显示的驱动芯片,常用于小型彩色显示屏,如圆形屏幕。这个项目是关于如何使用ESP32驱动GC9A01芯片来在圆形屏幕上显示图片,并且能根据预设时间自动更换显示内容,增加互动性和趣味性。 首先...

    GC-Prevue官方原版

    标题 "GC-Prevue官方原版" 暗示我们讨论的是一个专为电子制造行业设计的软件,用于查看和处理Gerber文件。Gerber文件是PCB(印制电路板)制造过程中的标准格式,它包含了PCB设计的各个层面的详细信息,如导电路径、...

    GC-Place软件及使用教程

    《GC-Place软件及使用教程》是一份详细指导用户如何安装和操作GC-Place软件的资源包,旨在帮助用户快速掌握这款专用于SMT(Surface Mount Technology)贴片机编程的工具。GC-Place在电子制造领域扮演着重要的角色,...

    gc2145 mipi 驱动

    《GC2145 MIPI驱动详解:在MTK Android 4.4系统中的应用与配置》 在Android操作系统中,驱动程序是连接硬件设备与操作系统内核的关键桥梁,它们负责管理和优化硬件资源,使软件应用程序能够充分利用硬件功能。本文...

    GC9307TFT显示屏驱动IC数据手册

    ### GC9307TFT显示屏驱动IC数据手册知识点总结 #### 一、GC9307概述 GC9307是一款专为a-Si TFT LCD设计的单芯片驱动器,支持240RGBx320分辨率及262K色显示,适用于多种小型至中型尺寸的触摸屏应用。此驱动IC旨在...

    GC7107CQ电压表 原理图PCB源文件

    【GC7107CQ电压表原理图PCB源文件】是一个与电子工程相关的主题,特别是关于电压测量设备的设计。GC7107CQ是一款集成电路,常用于电压检测和显示电路中,可能是数字电压表的核心部分。下面将详细讨论相关知识点。 1...

Global site tag (gtag.js) - Google Analytics