.NET Core CSharp初级篇 1-7

本节内容为类的生命周期

引言

对象究竟是一个什么东西?对于许多初学者而言,对象都是一个非常抽象的知识点。如果非要用一句话描述,我觉得“万物皆对象”是对于对象最全面的概述了。本节内容中,我们将以在富土康打工的张全蛋组装一台水果手机作为例子,详细的讲解面向对象的各个方面。

对象类的构造

“张全蛋,你去水果公司,把他们的组装零件需求清单带过来~,并且还要带上组装的技术说明书。”车间主任吆喝着叫张全蛋办事。张全蛋前往了水果公司,如愿以偿的拿到了他想要的东西,组装零件清单上写着:

  • amoled屏幕*1
  • 电池3000MA *1
  • CPU*1
  • 内存*1

技术说明上写着:

  • 组装零件:屏幕放置在顶部,电池在底部,中间夹着PCB板,PCB上面封住CPU和内存
  • 开机方法:长按开机键三秒

限于篇幅,我们只列举这些,你可以发现,我们的组装清单上面,事实上就是我们手机的组成部分,需要占用手机内部空间,并且是这个手机的重要组成参数。这就和我们类中的属性和字段的功能是一样的;而技术说明,是对于这里的具体操作,他们是一个工序,一个操作,并不是一个实体,因此他们就是和我们类中的函数是一个意思。

突然一位老员工对张全蛋说,其实啊,每一款水果手机都几乎没多大差别,你可以在机器中预设好内存大小和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 管理内存的区域,主要有三块,分别为:

  • 线程的堆栈,用于分配值类型实例。堆栈主要由操作系统管理,而不受垃圾收集器的控制,当值类型实例所在方法结束时,其存储单位自动释放。栈的执行效率高,但存储容量有限。
  • GC 堆,用于分配小对象实例。如果引用类型对象的实例大小小于 85000 字节,实例将被分配在 GC 堆上,当有内存分配或者回收时,垃圾收集器可能会对 GC 堆进行压缩,详情见后文讲述。
  • LOH(Large Object Heap)堆,用于分配大对象实例。如果引用类型对象的实例大小不小于 85000 字节时,该实例将被分配到 LOH 堆上,而 LOH 堆不会被压缩,而且只在完全 GC 回收时被回收。

对于分配在堆栈上的局部变量来说,操作系统维护着一个堆栈指针来指向下一个自由空间的地址,并且堆栈的内存地址是由高位到低位向下填充。

而对于引用类型的实例分配于托管堆上,而线程栈却是对象生命周期开始的地方。对 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 仍指向下一个新建对象的位置。注意,栈的分配是向

低地址扩展,而堆的分配是向高地址扩展。

最后,调用对象构造器,进行对象初始化操作,完成创建过程。该构造过程,又可细分为

以下几个环节:

  • 构造 FruitPhone 类型的 Type 对象,主要包括静态字段、方法表、实现的接口等,并将其

    分配在上文提到托管堆的 Loader Heap 上。
  • 初始化 p 的两个附加成员:TypeHandle 和 SyncBlockIndex。将 TypeHandl

    e 指针指向 Loader Heap 上的 MethodTable,CLR 将根据 TypeHandle 来定位具体的 Type;

    将 SyncBlockIndex 指针指向 Synchronization Block 的内存块,用于在多线程环境下对实例

    对象的同步操作。
  • 调用 FruitPhone 的构造器,进行实例字段的初始化。实例初始化时,会首先向上递归执

    行父类初始化,直到完成 System.Object 类型的初始化,然后再返回执行子类的初始化,直到

    执行 FruitPhone 类为止。以本例而言,初始化过程为首先执行 System.Object 类,直接执行 FruitPhone。最终,newobj 分配的托管堆的内存地址,被传递给 FruitPhone 的 thi

    s 参数,并将其引用传给栈上声明的 p。

上述过程,基本完成了一个引用类型创建、内存分配和初始化的整个流程,然而该过程只能看作是一个简化的描述,实际的执行过程更加复杂,涉及到一系列细化的过程和操作。

(插入内存图像)

补充

静态字段的内存分配和释放,又有何不同?

静态字段也保存在方法表中,位于方法表的槽数组后,其生命周期为从创建到 AppDomain

卸载。因此一个类型无论创建多少个对象,其静态字段在内存中也只有一份。静态字段只能由静

态构造函数进行初始化,静态构造函数确保在类型任何对象创建前,或者在任何静态字段或方法

被引用前执行,其详细的执行顺序请参考相关讨论。

对象的消亡

在这一部分,我们首先观察对象之死,以此反思和体味人类入世的哲学,两者相比较,也会给我们更多关于自己的启示。对象的生命周期由 GC 控制,其规则大概是这样:GC 管理所有的托管堆对象,当内存回收执行时,GC 检查托管堆中不再被使用的对象,并执行内存回收操作。不被应用程序使用的对象,指的是对象没有任何引用。关于如何回收、回收的时刻,以及遍历可回收对象的算法,是较为复杂的问题,我们将在 后续进行深度探讨。不过,这个回收的过程,同样使我们感慨。大自然就是那个看不见的 GC,造物而又终将万物回收,无法改变。我们所能做到的是,将生命的周期拓宽、延长、书写得更加精彩

如果我的文章帮到了你,请在博客园下面点一个推荐,在github项目页面点一颗星,谢谢

Reference

你必须知道的.NET

Github

BiliBili主页

WarrenRyan's Blog

博客园

.NET Core CSharp初级篇 类的生命历程的更多相关文章

  1. .NET Core CSharp初级篇 1-6 类的多态与继承

    .NET Core CSharp初级篇 1-6 本节内容为类的多态与继承 简介 终于讲到了面向对象三大特性中的两大特性--继承与多态.通过继承与多态,我们能很好的将类的拓展性发挥到了极致.在下面的内容 ...

  2. .NET Core CSharp初级篇 1-1

    .NET Core CSharp初级篇 1-1 本节内容是对于C#基础类型的存储方式以及C#基础类型的理论介绍 基础数据类型介绍 例如以下这句话:"张三是一名程序员,今年15岁重50.3kg ...

  3. NET Core CSharp初级篇 1-3面向对象

    .NET Core CSharp初级篇 1-3 本节内容为面向对象初级教程 类 简介 面向对象是整个C#中最核心最有特色的一个模块了,它很好的诠释了程序与现实世界的联系. 面向对象的三大特征:继承.多 ...

  4. .NET Core CSharp初级篇 1-5 接口、枚举、抽象

    .NET Core CSharp初级篇 1-5 本节内容类的接口.枚举.抽象 简介 问题 如果你需要表示星期或者是某些状态,使用字符串或者数字是否不直观? 你是否发现,无论何种电脑,它的USB口的设计 ...

  5. .NET Core CSharp初级篇 1-8泛型、逆变与协变

    .NET Core CSharp初级篇 1-8 本节内容为泛型 为什么需要泛型 泛型是一个非常有趣的东西,他的出现对于减少代码复用率有了很大的帮助.比如说遇到两个模块的功能非常相似,只是一个是处理in ...

  6. .NET Core CSharp初级篇 1-2 循环与判断

    .NET Core CSharp初级篇 1-2 本节内容循环与判断 循环 循环是一个在任何语言都是极为重要的语法,它可以用于很多东西,例如迭代数组等等.在C#中,语法层面的循环有:for , fore ...

  7. CSharp初级篇 1-4 this、索引器、静态、常量以及只读

    .NET Core CSharp初级篇 1-4 本节内容为this.索引器.静态.常量以及只读 简介 在之前的课程中,我们谈论过了静态函数和字段的一小部分知识,本节内容中,我们将详细的讲解关于对象操作 ...

  8. .NET Core CSharp 中级篇 2-1 装箱与拆箱

    .NET Core CSharp 中级篇 2-1 本节内容为装箱与拆箱 简介 装箱和拆箱是一个相对抽象的概念.你可以想象一下一堆满载货物的大卡车,他是由许多工人将货物集中堆放装入的,对于我们而言在没有 ...

  9. .NET Core CSharp 中级篇 2-2 List,ArrayList和Dictionary

    .NET Core CSharp 中级篇 2-2 本节内容为List,ArrayList,和Dictionary 简介 在此前的文章中我们学习了数组的使用,但是数组有一个很大的问题就是存储空间不足,我 ...

随机推荐

  1. Python连载10-os包函数(续)

    一.os包(接连载9) 1.函数:system() (1)用法:运行系统shell命令 (2)格式:os.system(系统命令) (3)返回值:打开一个shell或终端界面 (4)注意:一般是用su ...

  2. 设置windows服务依赖项

    场景还原:python2.7开发的项目,制作成了windows服务,随系统启动.系统重启后发现服务未能自动启动,检查事件查看器日志发现服务先于Mysql数据库服务启动,由于服务中必须对MySQL进行访 ...

  3. 深入理解Java的浅克隆与深克隆

    前言 克隆,即复制一个对象,该对象的属性与被复制的对象一致,如果不使用Object类中的clone方法实现克隆,可以自己new出一个对象,并对相应的属性进行数据,这样也能实现克隆的目的. 但当对象属性 ...

  4. 关于Git GUI的使用方式

    1.选择Clone Existing Repository 2.选择clone地址和存放位置,然后clone 3失败 4如果失败,让对方去这里(github的界面)邀请下,如果是自己就不用 5然后等待 ...

  5. spring 5.x 系列第2篇 —— springmvc基础 (代码配置方式)

    文章目录 一.搭建hello spring工程 1.1 项目搭建 1.2 相关注解说明 二.配置自定义拦截器 三.全局异常处理 四.参数绑定 4.1 参数绑定 4.2 关于日期格式转换的三种方法 五. ...

  6. CTF干货合集

    CTF练习平台 http://hackinglab.cn/ 网络信息安全攻防学习平台 http://captf.com/ ctf题目 http://oj.xctf.org.cn/ XCTF_OJ练习平 ...

  7. 微信小程序内链微信公众号的方法

    最近接了一个需求,要求在微信小程序内部添加关注微信公众号的方式并给出解决方案,经过几天的翻官网文档,查周边资料,问资深技术员,初步给出两个解决方案: 题外话: 搬砖容易,建设难,搬砖的小伙伴请注明文章 ...

  8. mysql数据库在linux下的导出和导入及每天的备份

    mysql数据库的导出,导入 1. 导出数据库为sql文件 mysqldump 数据库名 -uroot -p > xxx.sql 导出数据表结构和数据 eg.   mysqldump cloud ...

  9. Programming In Lua 第一章

    1,Lua可以嵌入其他应用程序(如CGILua或IUPLua). 2,lua代码的语句,分号是可以省略的.同一行可以有多条lua语句,最好用分号隔开(当然也可以不隔开) 3,外壳与lua解释器的区别. ...

  10. SqlHelper(基础)

    using System; using System.Collections.Generic; using System.Data; using System.Data.SqlClient; usin ...