400楼影吧,颤栗100步,再见先生国语版
本节内容为类的生命周期
对象究竟是一个什么东西?对于许多初学者而言,对象都是一个非常抽象的知识点。如果非要用一句话描述,我觉得“万物皆对象”是对于对象最全面的概述了。本节内容中,我们将以在富土康打工的张全蛋组装一台水果手机作为例子,详细的讲解面向对象的各个方面。
“张全蛋,你去水果公司,把他们的组装零件需求清单带过来~,并且还要带上组装的技术说明书。”车间主任吆喝着叫张全蛋办事。张全蛋前往了水果公司,如愿以偿的拿到了他想要的东西,组装零件清单上写着:
技术说明上写着:
限于篇幅,我们只列举这些,你可以发现,我们的组装清单上面,事实上就是我们手机的组成部分,需要占用手机内部空间,并且是这个手机的重要组成参数。这就和我们类中的属性和字段的功能是一样的;而技术说明,是对于这里的具体操作,他们是一个工序,一个操作,并不是一个实体,因此他们就是和我们类中的函数是一个意思。
突然一位老员工对张全蛋说,其实啊,每一款水果手机都几乎没多大差别,你可以在机器中预设好内存大小和cpu的型号,这样你就可以直接将模具做好了。面对这种情况,张全蛋想出了一个绝妙的方法,那就是在构造函数中传入参数。
因此我们可以构造出这样一个类
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 } }
对象就像个体的人,生而入世,死而离世。我们的故事就从对象之生开始吧。首先,看看在上面的例子中,一个对象是如何出生的。
fruitphone p = new fruitphone(2,"a12");
我们通过调用构造函数,成功的创造了一个手机对象,在手机被创建的同时,虽然我们还没有组装好屏幕一类的,但是我们在手机模具中也需要预留他们的空间,因此在对象实例化的时候,其内部的每个字段都会被初始化。
对于屏幕和电池一类的,我们后续可能会根据成本等等进行调整,对
象的出生也只是完成了对必要字段的初始化操作,其他数据要通过后面的操作来完成。例如对属性赋值,通过方法获取必要的信息等。
关于内存的分配,首先应该了解分配在哪里的问题。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,造物而又终将万物回收,无法改变。我们所能做到的是,将生命的周期拓宽、延长、书写得更加精彩
你必须知道的.net
如对本文有疑问,请在下面进行留言讨论,广大热心网友会与你互动!! 点击进行留言回复
Net Core Web Api项目与在NginX下发布的方法
asp.net core3.1 引用的元包dll版本兼容性问题解决方案
IdentityServer4实现.Net Core API接口权限认证(快速入门)
ASP.NET Core MVC通过IViewLocationExpander扩展视图搜索路径的实现
网友评论