《精通C#》第十三章 对象的生命周期

在C#中,程序员无法直接在C#中删除一个托管对象,因为C#不提供这个功能,那么类的实例就需要通过CLR调用垃圾回收机制进行清除,回收内存。.NET垃圾回收器会压缩空的内存块来实现优化,为了辅助这一功能,托管堆会保存一个指针,它指向下一个对象将被分配的位置。那么CLR是如何使用垃圾回收机制呢?首先,类实例化之后具体的对象会被分配到一块叫托管堆的内存区域上,将托管堆中的对象的引用地址返回给函数中的引用变量,引用变量保存在栈内,要使用对象中的方法只需要使用点操作就可以。特别需要说一下的是,结构是数值类型,它直接分配在栈上(所以的数值类都是这样的,只有引用类型才会保存在托管堆上)。实例化结束之后,垃圾回收器会在一个对象从代码库的任何部分都不可访问的时候,将它从堆中删除,例:

static void MakeCar()

{

Car mycar=new Car();

}

在例子中,Car的引用mycar直接在MakeCar中创建,并没有被传到该方法以外的作用域的,因此,在这个方法调用结束之后,这个对象就不会再被访问,此时它就是垃圾回收器的回收目标,但是,要知道,一个对象在失去意义之后并不会立即被删除,CLR调用垃圾回收器的标准是:在创建对象时,先判断对象所需要的内存的大小,在判断目前的托管堆是否有足够的内存保存它,当托管堆没有足够的内存时,CLR就会调用垃圾回收器进行垃圾回收,因此,在对象失去意义之后还需要等待CLR调用垃圾回收器时才能被删除。那么CLR是如何判断对象所需的内存,以及堆的内存是否够用?当C#编译器在遇到new关键字时,它会自动在方法的实现中加上一条CIL newobj 的指令,它会计算分配对象所需的总内存,检查堆的内存空间,如果内存足够,则调用类型的构造函数,最终将内存中的新变量的引用返回给调用者,它的地址是下一个对象指针的最后位置,若是内存不足,则CLR调用垃圾回收器释放内存。在回调地址之后,就将对象的指针指向下一个可用的地址。那么垃圾回收器如何判断一个对象是否还在使用,这就要介绍一个应用程序根,所谓的根,说白了就是存储堆上的对象的引用地址的存储位置,在回收过程中运行库会对对象进行判断,判断程序是否还可以访问它们,也就是说它们的根是否还存在,若是不存在,则标记为垃圾,进而被清除对象,CLR压缩内存,指针指向正确的位置。但是若是每一次进行垃圾回收的时候都要对托管堆上的所以数据进行一次判定,这种方法就太过于耗时耗力了,于是就有了代,代的设计思路是:对象在堆上存在的时间越长就越可能被保留。所以就将堆上的对象共分为0-2的3代,垃圾回收器在运行的时候,首先会检测0代的对象,并且将那些不需要的对象释放,空出内存,若是空出的内存不够,则会往上面一级的1级代进行检测,以此类推,直到获取所需要的内存大小为止,而在这之后,0代上没被删除的对象就会被标记为1代,一代中未被删除的对象就标记为2代,但是2代还是二代,因为这是上限。在这里还得说一下,其实垃圾回收器使用的两个堆,我们所说的是小对象堆,还有一个大对象堆,他存储的是大于85k的对象,因为它的内容太大,对它进行修改的话,花费代价太大,所以在垃圾回收器极少会对它进行修改。

以上是垃圾回收器自动对对上面的数据进行回收,这并不需要人为的进行操控,但是这是对于托管在托管堆上面的对象,若是有些数据不是托管的资源呢?.NET提供了一个System.GC的类类型,它可以通过编程使用一些静态成员与垃圾回收器进行交互,这种行为也叫作强制垃圾回收,它可以由我们自己决定什么时候释放某个对象的资源,而不用被动等待垃圾回收器运行,一般来说在不希望接下来的代码被垃圾回收器打断的运行时候(垃圾回收器的运行时间是不确定的),或者我需要一次性分配很多的对象的时候都会用到强制回收,强制回收使用GC.Collect()方法,在它的后面必须要调用GC.WaitForPendingFinalize(), 另外,使用Finalize()构建可终结对象,当应用程序的应用程序域从内存中卸载的话,CLR就会自动调用它的生命周期中所创建的每一个可终结对象的终结器进行强制回收,重写Finalize()无法像普通的类一样,它需要类似C++的析构语法,此外终结器还需在名称之前加~,他不接受访问修饰符,不接受参数,不支持重载,但是使用这种方式的话,需要两次的来及回收才能真正的释放该资源,并且由于是额外的处理,所以速度回变得非常慢。所以就可以考虑构建一个可处置对象,构建可处置对象需要实现IDisposable,这个方法不止可以释放一个对象的非托管资源,而且还可以对任何它包含的可处置对象调用Dipose(),使用这种方法可以有自己调用释放内存,若是忘记调用,也会有垃圾回收器进行释放,所以这种方式在我看来会更安全好用一些。在C#类中还为实现了IDisposable的接口提供了一类语法:using,使用这种语法的好处在于,可以由Dispose()调用扩展try/catch结构,例如:using(class c=new class()){}在编译之后,与class c=new class();try{}catch(){};是等同的。

在这一个章节内,我觉得还有一个非常使用的泛型类Lazy<>,但凡被这个类所定义的数据在代码库实际使用它之前是不会被创建的,这样就可以使一些不常使用的大数据在实例化对象的时候不同时被创建,进而占用内存空间。例:

class song{

public string Artist{get;set;}

public string TrackName{get;set;}

public double TrackLength{get;set;}

}

class AllTracks

{

private Song[] allSongs=new Song[10000];

public AllTracks()

{

Console.WriteLine();

}

class MediaPlayer()

{

public void Play(){};

private AllTracks allsongs=new AllTracks();

public AllTracks GetAllTracks()

{

return allSongs;

}

}

}

main(){

MediaPlayer m=new MediaPlayer();//在这个时候,已经间接的创建10000个歌曲对象了

}

若是将MediaPlayer修改为:

class MediaPlayer()

{

public void Play(){};

private Lazy<AllTracks> allsongs=new Lazy<AllTracks>();

public AllTracks GetAllTracks()

{

return allSongs.Value;

}

}

调用方式就要改为:

main(){

MediaPlayer m=new MediaPlayer();//在这个时候,未创建10000个歌曲对象了

AllTracks songs=m.GetAllTracks();//此时,歌曲对象才创建

}

时间: 2024-10-10 16:47:38

《精通C#》第十三章 对象的生命周期的相关文章

第十一章 对象的生命周期

第11章 对象的生命周期 11.1  创建对象的方式 用new语句创建对象 运用反射手段,调用java.lang.Class 或者 java.lang.Constructor 类的newInstance()实例方法. 调用对象的clone()方法. 运用反序列化手段 11.2 构造方法 在多数情况下,初始化对象的最终步骤是去调用这个对象的构造方法.构造方法负责对象的初始化工作,为实例变量赋予合适的初始化值. 构造方法满足的语法规则: 方法名必须与类名相同 1 public class Sampl

《深入Java虚拟机学习笔记》- 第7章 类型的生命周期

一.类型生命周期的开始 如图所示 初始化时机 所有Java虚拟机实现必须在每个类或接口首次主动使用时初始化: 以下几种情形符合主动使用的要求: 当创建某个类的新实例时(或者通过在字节码中执行new指令,或者通过不明确的创建.反射.克隆和反序列化): 当调用某个类的静态方法时(即在字节码中执行invokestatic指令): 当使用某个类或接口的静态字段,或者对该字段赋值时(用final修饰的静态字段除外,它被初始化为一个编译时常量表达式): 当调用Java API中的某些反射方法: 当初始化某个

MyBatis入门——核心对象的生命周期(SqlSessionFactoryBuilder,SqlSessionFactory, SqlSession和Mapper)

二.核心对象的生命周期 从上一篇文章中,我们可以看出mybatis操作数据库主要使用了4个核心对象:SqlSessionFactoryBuilder,SqlSessionFactory, SqlSession和Mapper.那么,在软件系统中,这个几个对象的生命周期是什么样的呢?什么时候创建?什么时候销毁? 1. SqlSessionFactoryBuilder SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder(

hibernate中持久化对象的生命周期(三态:自由态,持久态,游离态 之间的转换)

三态的基本概念: 1,  暂时状态(Transient):也叫自由态,仅仅存在于内存中,而在数据库中没有对应数据.用new创建的对象,它没有持久化,没有处于Session中,处于此状态的对象叫暂时对象: 2,  持久化状态(Persistent):与session关联而且在数据库中有对应数据.已经持久化,添?到了Session缓存中.如通过hibernate语句保存的对象.处于此状态的对象叫持久对象: 3,  游离状态(Detached):持久化对象脱离了Session的对象.如Session缓

Java对象的生命周期与作用域的讨论(转)

导读: Java对象的生命周期大致包括三个阶段:对象的创建,对象的使用,对象的清除.因此,对象的生命周期长度可用如下的表达式表示:T = T1 + T2 +T3.其中T1表示对象的创建时间,T2表示对象的使用时间,而T3则表示其清除时间.由此,我们可以看出,只有T2是真正有效的时间,而T1.T3则是对象本身的开销.下面再看看T1.T3在对象的整个生命周期中所占的比例. 我们知道,Java对象是通过构造函数来创建的,在这一过程中,该构造函数链中的所有构造函数也都会被自动调用.另外,默认情况下,调用

[原创]java WEB学习笔记94:Hibernate学习之路---session 的管理,Session 对象的生命周期与本地线程绑定

本博客的目的:①总结自己的学习过程,相当于学习笔记 ②将自己的经验分享给大家,相互学习,互相交流,不可商用 内容难免出现问题,欢迎指正,交流,探讨,可以留言,也可以通过以下方式联系. 本人互联网技术爱好者,互联网技术发烧友 微博:伊直都在0221 QQ:951226918 -----------------------------------------------------------------------------------------------------------------

C++ 匿名对象的生命周期

//匿名对象的生命周期 #define _CRT_SECURE_NO_WARNINGS #include<iostream> using namespace std; class Point{ public: Point(){ cout << "自定义的无参构造函数被调用了1" << endl; } ~Point(){ cout << "自定义的析构函数被调用了2" << endl; } }; void P

Java 对象的生命周期

Java对象的生命周期 在Java中,对象的生命周期包含下面几个阶段: 1.      创建阶段(Created) 2.      应用阶段(In Use) 3.      不可见阶段(Invisible) 4.      不可达阶段(Unreachable) 5.      收集阶段(Collected) 6.      终结阶段(Finalized) 7.      对象空间重分配阶段(De-allocated) 图1. JavaObject Life Cycle 1.创建阶段(Create

sqlite3语句对象的生命周期--举例说明

 语句对象的生命周期:  1.使用sqlite3_prepare_v2或相关的函数创建这个对象  2.使用sqlite3_bind_*()给宿主参数绑定值  3.通过调用sqlite3_step一次或多次来执行这个sql  4.使用sqlite3_reset()重置这个语句,然后回到第2步,这个过程做0次或多次  5.使用sqlite3_finalize()销毁这个对象 例子: 1 //查询指定商品 2 - (Product *)selectProductById:(int)proId { 3