本节内容为类的生命周期git
对象到底是一个什么东西?对于许多初学者而言,对象都是一个很是抽象的知识点。若是非要用一句话描述,我以为“万物皆对象”是对于对象最全面的概述了。本节内容中,咱们将以在富土康打工的张全蛋组装一台水果手机做为例子,详细的讲解面向对象的各个方面。github
“张全蛋,你去水果公司,把他们的组装零件需求清单带过来~,而且还要带上组装的技术说明书。”车间主任吆喝着叫张全蛋办事。张全蛋前往了水果公司,如愿以偿的拿到了他想要的东西,组装零件清单上写着:算法
技术说明上写着:数组
限于篇幅,咱们只列举这些,你能够发现,咱们的组装清单上面,事实上就是咱们手机的组成部分,须要占用手机内部空间,而且是这个手机的重要组成参数。这就和咱们类中的属性和字段的功能是同样的;而技术说明,是对于这里的具体操做,他们是一个工序,一个操做,并非一个实体,所以他们就是和咱们类中的函数是一个意思。多线程
忽然一位老员工对张全蛋说,其实啊,每一款水果手机都几乎没多大差异,你能够在机器中预设好内存大小和CPU的型号,这样你就能够直接将模具作好了。面对这种状况,张全蛋想出了一个绝妙的方法,那就是在构造函数中传入参数。ide
所以咱们能够构造出这样一个类函数
class FruitPhone { public FruitPhone(int msize,string cpuType) { CpuType = cpuType; MemSize = msize; } public string ScreenType{get;set} public string CpuType{get;set;} public int MemSize{get;set;} public int Battery{get;set;} void Make() { //todo } void Open() { //todo } }
对象就像个体的人,生而入世,死而离世。咱们的故事就从对象之生开始吧。首先,看看在上面的例子中,一个对象是如何出生的。ui
FruitPhone p = new FruitPhone(2,"A12");
咱们经过调用构造函数,成功的创造了一个手机对象,在手机被建立的同时,虽然咱们尚未组装好屏幕一类的,可是咱们在手机模具中也须要预留他们的空间,所以在对象实例化的时候,其内部的每一个字段都会被初始化。spa
对于屏幕和电池一类的,咱们后续可能会根据成本等等进行调整,对
象的出生也只是完成了对必要字段的初始化操做,其余数据要经过后面的操做来完成。例如对属性赋值,经过方法获取必要的信息等。操作系统
关于内存的分配,首先应该了解分配在哪里的问题。CLR 管理内存的区域,主要有三块,分别为:
对于分配在堆栈上的局部变量来讲,操做系统维护着一个堆栈指针来指向下一个自由空间的地址,而且堆栈的内存地址是由高位到低位向下填充。
而对于引用类型的实例分配于托管堆上,而线程栈倒是对象生命周期开始的地方。对 32 位处理器来讲,应用程序完成进程初始化后,CLR 将在进程的可用地址空间上分配一块保留的地址空间,它是进程(每一个进程可以使用 4GB)中可用地址空间上的一块内存区域,但并不对应于任何物理内存,这块地址空间便是托管堆。托管堆又根据存储信息的不一样划分为多个区域,其中最重要的是垃圾回收堆(GC Heap)和加载堆(Loader Heap),GC Heap 用于存储对象实例,受 GC 管理;Loader Heap 又分为 High-Frequency Heap、Low-Frequency Heap 和 Stub Heap,不一样的堆上又存储不一样的
信息。Loader Heap 最重要的信息就是元数据相关的信息,也就是 Type 对象,每一个 Type 在 Loader Heap 上体现为一个 Method Table(方法表),而 Method Table 中则记录了存储的元数据信息,例如基类型、静态字段、实现的接口、全部的方法等等。Loader Heap 不受 GC 控制,其生命周期为从建立到 AppDomain 卸载。
对于本例中的对象建立,首先会在栈中声明一个指向堆中数据的指针(引用),它占用4个字节,而后调用newobj指令,搜索该类是否含有父类,若是有,则从父类开始分配内存,对于本例中,FruitPhone对象所须要的内存为4字节的string引用两个,4字节的int*2。实例对象所占的字节总数还要加上对象附加成员所需的字节总数,其中附加成员包括 TypeHandle 和 SyncBlockIndex,共计 8 字节(在 32 位 CPU 平台下),共计24字节。
CLR 在当前 AppDomain 对应的托管堆上搜索,找到一个未使用的 20 字节的连续空间,并为其分配该内存地址。事实上,GC 使用了很是高效的算法来知足该请求,NextObjPtr 指针只须要向前推动 20 个字节,并清零原 NextObjPtr 指针和当前 NextObjPtr 指针之间的字节,
而后返回原 NextObjPtr 指针地址便可,该地址正是新建立对象的托管堆地址,也就是p引用指向的实例地址。而此时的 NextObjPtr 仍指向下一个新建对象的位置。注意,栈的分配是向
低地址扩展,而堆的分配是向高地址扩展。
最后,调用对象构造器,进行对象初始化操做,完成建立过程。该构造过程,又可细分为
如下几个环节:
上述过程,基本完成了一个引用类型建立、内存分配和初始化的整个流程,然而该过程只能看做是一个简化的描述,实际的执行过程更加复杂,涉及到一系列细化的过程和操做。
(插入内存图像)
静态字段的内存分配和释放,又有何不一样?
静态字段也保存在方法表中,位于方法表的槽数组后,其生命周期为从建立到 AppDomain
卸载。所以一个类型不管建立多少个对象,其静态字段在内存中也只有一份。静态字段只能由静
态构造函数进行初始化,静态构造函数确保在类型任何对象建立前,或者在任何静态字段或方法
被引用前执行,其详细的执行顺序请参考相关讨论。
在这一部分,咱们首先观察对象之死,以此反思和体味人类入世的哲学,二者相比较,也会给咱们更多关于本身的启示。对象的生命周期由 GC 控制,其规则大概是这样:GC 管理全部的托管堆对象,当内存回收执行时,GC 检查托管堆中再也不被使用的对象,并执行内存回收操做。不被应用程序使用的对象,指的是对象没有任何引用。关于如何回收、回收的时刻,以及遍历可回收对象的算法,是较为复杂的问题,咱们将在 后续进行深度探讨。不过,这个回收的过程,一样使咱们感慨。大天然就是那个看不见的 GC,造物而又终将万物回收,没法改变。咱们所能作到的是,将生命的周期拓宽、延长、书写得更加精彩
若是个人文章帮到了你,请在博客园下面点一个推荐,在github项目页面点一颗星,谢谢
你必须知道的.NET