.NET Core CSharp初级篇 类的生命历程
.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
.NET Core CSharp初级篇 类的生命历程的更多相关文章
- .NET Core CSharp初级篇 1-6 类的多态与继承
.NET Core CSharp初级篇 1-6 本节内容为类的多态与继承 简介 终于讲到了面向对象三大特性中的两大特性--继承与多态.通过继承与多态,我们能很好的将类的拓展性发挥到了极致.在下面的内容 ...
- .NET Core CSharp初级篇 1-1
.NET Core CSharp初级篇 1-1 本节内容是对于C#基础类型的存储方式以及C#基础类型的理论介绍 基础数据类型介绍 例如以下这句话:"张三是一名程序员,今年15岁重50.3kg ...
- NET Core CSharp初级篇 1-3面向对象
.NET Core CSharp初级篇 1-3 本节内容为面向对象初级教程 类 简介 面向对象是整个C#中最核心最有特色的一个模块了,它很好的诠释了程序与现实世界的联系. 面向对象的三大特征:继承.多 ...
- .NET Core CSharp初级篇 1-5 接口、枚举、抽象
.NET Core CSharp初级篇 1-5 本节内容类的接口.枚举.抽象 简介 问题 如果你需要表示星期或者是某些状态,使用字符串或者数字是否不直观? 你是否发现,无论何种电脑,它的USB口的设计 ...
- .NET Core CSharp初级篇 1-8泛型、逆变与协变
.NET Core CSharp初级篇 1-8 本节内容为泛型 为什么需要泛型 泛型是一个非常有趣的东西,他的出现对于减少代码复用率有了很大的帮助.比如说遇到两个模块的功能非常相似,只是一个是处理in ...
- .NET Core CSharp初级篇 1-2 循环与判断
.NET Core CSharp初级篇 1-2 本节内容循环与判断 循环 循环是一个在任何语言都是极为重要的语法,它可以用于很多东西,例如迭代数组等等.在C#中,语法层面的循环有:for , fore ...
- CSharp初级篇 1-4 this、索引器、静态、常量以及只读
.NET Core CSharp初级篇 1-4 本节内容为this.索引器.静态.常量以及只读 简介 在之前的课程中,我们谈论过了静态函数和字段的一小部分知识,本节内容中,我们将详细的讲解关于对象操作 ...
- .NET Core CSharp 中级篇 2-1 装箱与拆箱
.NET Core CSharp 中级篇 2-1 本节内容为装箱与拆箱 简介 装箱和拆箱是一个相对抽象的概念.你可以想象一下一堆满载货物的大卡车,他是由许多工人将货物集中堆放装入的,对于我们而言在没有 ...
- .NET Core CSharp 中级篇 2-2 List,ArrayList和Dictionary
.NET Core CSharp 中级篇 2-2 本节内容为List,ArrayList,和Dictionary 简介 在此前的文章中我们学习了数组的使用,但是数组有一个很大的问题就是存储空间不足,我 ...
随机推荐
- 深度强化学习day01初探强化学习
深度强化学习 基本概念 强化学习 强化学习(Reinforcement Learning)是机器学习的一个重要的分支,主要用来解决连续决策的问题.强化学习可以在复杂的.不确定的环境中学习如何实现我们设 ...
- Django学习笔记(20)——BBS+Blog项目开发(4)Django如何使用Bootstrap
本文学习如何通过Django使用Bootstrap.其实在之前好几个Django项目中已经尝试使用过了Bootstrap,而且都留有学习记录,我已经大概有了一个大的框架,那么本文就从头再走一遍流程,其 ...
- mpvue 开发小程序接口数据统一管理
mpvue项目里做API与数据分离统一管理 小程序里请求数据接口使用wx:request,因为考虑项目比较大,最好把wx:request封装起来,统一使用管理 utils.js 配置开发环境和线上环境 ...
- spark开发常见问题之一:java.io.IOException: Could not locate executable null\bin\winutils.exe in the Hadoop binaries.
最近在学习研究pyspark机器学习算法,执行代码出现以下异常: 19/06/29 10:08:26 ERROR Shell: Failed to locate the winutils binary ...
- Netty源码分析--创建Channel(三)
恩~,没错,其实这一篇才是真正的开始分析源码,你打我呀~. 先看一下我Netty的启动类 private void start() throws Exception { EventLoopGroup ...
- ZTree简单粗暴快速使用
是什么:功能强大的树形插件 tip:查资料时痛苦的地方,自我改进 1.没有注明版本:版本不对应导致配置完成后各种无端的错误,特别难查找,运气好能找到英文的解答 2.没有写明配置文件,或者不指明配置文件 ...
- js中新增动态属性
var cc = 'hell' var mm = { [cc](){ alert(33) } } mm.hell() 使用的就是数组形式
- 了解Kubernetes主体架构(二十八)
前言 Kubernetes的教程一直在编写,目前已经初步完成了以下内容: 1)基础理论 2)使用Minikube部署本地Kubernetes集群 3)使用Kubeadm创建集群 接下来还会逐步完善本教 ...
- 授权公钥登录,sudo权限脚本
#!/bin/bash############################################################### File Name: key_auth.sh# V ...
- SpringCloud系列——限流、熔断、降级
前言 分布式环境下,服务直接相互调用,一个复杂的业务可能要调用多个服务,例如A -> B -> C -> D,当某个服务出现异常(调用超时.调用失败等)将导致整个流程阻塞崩溃,严重的 ...