.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. SYN591型 多功能数字面板表

       SYN591型 多功能数字面板表 多功能数字面板表数字面板表使用说明视频链接: http://www.syn029.com/h-pd-248-0_310_44_-1.html 请将此链接复制到浏 ...

  2. You can't specify target table 'tbl_students' for update in FROM clause错误

    此问题只出现在mysql中 oracle中无此问题 在同一语句中,当你在select某表的数据后,不能update这个表,如: DELETE FROM tbl_students WHERE id NO ...

  3. 你一定能看懂的JDK动态代理

    前言:阅读这篇文章前,一定要知道什么是代理模式,具体可以参考这篇文章<设计模式(一):代理模式>. 在<设计模式(一):代理模式>一文中说了,程序员思思买书有两种选择:一种是选 ...

  4. 有关Html页面节点的简单理解

    这是之前研究web前端的一点经验,主要针对刚入门还没怎么研究的朋友. 因为我发现我在用js,css参与过网站开发项目后仍然没有理解文本节点与普通节点的差别,所以记下来拿来分享一下. 先上结论:< ...

  5. JVM 参数类型

    标准参数 -help -server -client -version -showversion -cp -classpath X参数 非标准化参数(在各个JDK版本中可能会变,但是变动比较小) -X ...

  6. 怎么用Hostwinds搭建Wordpress博客网站(超详细图文教程)

    Hostwinds 成立于 2010 年,在主机托管行业算是一个比较新的品牌,但是,凭借丰富的产品线.卓越的服务器性能.良好的客户支持,以及低廉实惠的价格,他们受到了广大客户的喜爱,并多次获得行业重要 ...

  7. kubernetes实战篇之helm完整示例

    系列目录 构建一个 Helm Chart 下面我们通过一个完整的示例来学习如何使用 Helm 创建.打包.分发.安装.升级及回退Kubernetes应用. 创建一个名为 mychart 的 Chart ...

  8. Linux服务器使用Docker部署.net Core项目

    发布ASP.NET Core项目 和普通的项目发布一样,将项目发布到目标文件夹中 构建Dockerfile文件 在目标文件根目录新建Dockerfile文件(没有后缀) FROM microsoft/ ...

  9. 小白开学Asp.Net Core 《四》

    小白开学Asp.Net Core<三>                               —— 使用AspectCore-Framework 一.AspectCore-Frame ...

  10. 源码阅读 - java.util.concurrent (三)ConcurrentHashMap

    在java.util.concurrent包中提供了一个线程安全版本的Map类型数据结构:ConcurrentMap.本篇文章主要关注ConcurrentMap接口以及它的Hash版本的实现Concu ...