.NET 的内存管理机制(四)

简介: 结合实体案例分析对象初始化过程
代码案例
public class UserInfo
{
private Int32 age = -1;
private char level = 'A';
}
public class User
{
private Int32 id;
private UserInfo user;
}
public class VIPUser : User
{
public bool isVip;
public bool IsVipUser()
{
return isVip;
}
public static void Main()
{
VIPUser aUser;
aUser = new VIPUser();
aUser.isVip = true;
Console.WriteLine(aUser.IsVipUser());
}
}
将上述代码的执行过程,反编译为 IL 语言可知:new 关键字被编译为 newobj 指令来完成对象创建工作,进而调用类型的构造器来完成其初始化操作,然后我们描述执行的具体过程:

首先,将声明一个引用类型变量 aUser:

VIPUser aUser;

它仅是一个引用(指针),保存在线程的堆栈上,占用 4Byte 的内存空间,将用于保存 VIPUser 对象的有效地址,其执行过程正是上文描述的在线程栈上的分配过程。此时 aUser 未指向任何有效的实例,因此被自行初始化为 null,试图对 aUser 的任何操作将抛出 NullReferenceException 异常。

接着,通过 new 操作执行对象创建:
aUser = new VIPUser();

如上文所言,该操作对应于执行 newobj 指令,其执行过程又可细分为以下几步:

1、CLR 按照其继承层次进行搜索,计算类型及其所有父类的字段,该搜索将一直递归到System.Object 类型,并返回字节总数,以本例而言类型 VIPUser 需要的字节总数为 15Byte,具体计算为:VIPUser 类型本身字段 isVip(bool 型)为 1Byte;父类 User 类型的字段id(Int32 型)为 4Byte,字段 user 保存了指向 UserInfo 型的引用,因此占 4Byte,而同时还要为 UserInfo 分配 6Byte 字节的内存。

实例对象所占的字节总数还要加上对象附加成员所需的字节总数,其中附加成员包括 TypeHandle 和 SyncBlockIndex,共计 8 字节(在 32 位 CPU 平台下)。因此,需要在托管堆上分配的字节总数为 23 字节,而堆上的内存块总是按照 4Byte 的倍数进行分配,因此本例中将分配 24字节的地址空间。

2、CLR 在当前 AppDomain 对应的托管堆上搜索,找到一个未使用的 20 字节的连续空间,并为其分配该内存地址。事实上,GC 使用了非常高效的算法来满足该请求,NextObjPtr 指针只需要向前推进 20 个字节,并清零原 NextObjPtr 指针和当前 NextObjPtr 指针之间的字节,然后返回原 NextObjPtr 指针地址即可,该地址正是新创建对象的托管堆地址,也就是 aUser 引用指向的实例地址。而此时的 NextObjPtr 仍指向下一个新建对象的位置。注意,栈的分配是向低地址扩展,而堆的分配是向高地址扩展。

注意:另外,实例字段的存储是有顺序的,由上到下依次排列,父类在前子类在后。在上述操作时,如果试图分配所需空间而发现内存不足时,GC 将启动垃圾收集操作来回收垃圾对象所占的内存。

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

(a)构造 VIPUser 类型的 Type 对象,主要包括静态字段、方法表、实现的接口等,并将其分配在上文提到托管堆的 Loader Heap 上。

(b)初始化 aUser 的两个附加成员:TypeHandle 和 SyncBlockIndex。将 TypeHandle 指针指向 Loader Heap 上的 MethodTable,CLR 将根据 TypeHandle 来定位具体的 Type;将SyncBlockIndex 指针指向 Synchronization Block 的内存块,用于在多线程环境下对实例对象的同步操作。

(c)调用 VIPUser 的构造器,进行实例字段的初始化。实例初始化时,会首先向上递归执行父类初始化,直到完成 System.Object 类型的初始化,然后再返回执行子类的初始化,直到执行 VIPUser 类为止。以本例而言,初始化过程为首先执行 System.Object 类,再执行 User类,最后才是 VIPUser 类。最终,newobj 分配的托管堆的内存地址,被传递给 VIPUser 的 this 参数,并将其引用传给栈上声明的 aUser。
上述过程,基本完成了一个引用类型创建、内存分配和初始化的整个流程,然而该过程只能看作是一个简化的描述,实际的执行过程更加复杂,涉及到一系列细化的过程和操作。

对象创建并初始化之后,内存的布局,可以表示为:

20211220114019.png

总结

综上分析可知,在托管堆中增加新的实例对象,只是将 NextObjPtr 指针增加一定的数值,再次新增的对象将分配在当前 NextObjPtr 指向的内存空间,因此在托管堆栈中,连续分配的对象在内存中一定是连续的,这种分配机制非常高效。

相关文章
|
4天前
|
存储 开发框架 .NET
"揭秘.NET内存奥秘:从CIL深处窥探值类型与引用类型的生死较量,一场关于速度与空间的激情大戏!"
【8月更文挑战第16天】在.NET框架中,通过CIL(公共中间语言)可以深入了解值类型与引用类型的内存分配机制。值类型如`int`和`double`直接在方法调用堆栈上分配,访问迅速,生命周期随栈帧销毁而结束。引用类型如`string`在托管堆上分配,堆栈上仅存储引用,CLR负责垃圾回收,确保高效且自动化的内存管理。
17 6
|
4天前
|
存储 缓存 编译器
Linux源码阅读笔记06-RCU机制和内存优化屏障
Linux源码阅读笔记06-RCU机制和内存优化屏障
|
4天前
|
缓存 程序员
封装一个给 .NET Framework 用的内存缓存帮助类
封装一个给 .NET Framework 用的内存缓存帮助类
|
4天前
|
缓存 开发框架 .NET
看看 Asp.net core Webapi 项目如何优雅地使用内存缓存
看看 Asp.net core Webapi 项目如何优雅地使用内存缓存
|
7天前
|
NoSQL Redis
Redis——设置最大内存 | key淘汰机制
Redis——设置最大内存 | key淘汰机制
19 0
|
1月前
|
存储 缓存 监控
Flink内存管理机制及其参数调优
Flink内存管理机制及其参数调优
|
1月前
|
存储 算法 调度
深入理解操作系统的内存管理机制
【7月更文挑战第12天】本文将深入探讨操作系统中至关重要的内存管理机制。内存是计算机系统中宝贵的资源,其管理效率直接影响系统性能。我们将从内存管理的基本原理出发,逐步解析分页和分段技术、虚拟内存的概念以及内存分配策略等核心内容。通过实例分析,揭示现代操作系统如何优化内存使用,提高系统响应速度及资源利用效率。
|
1月前
|
存储 缓存 算法
操作系统中的内存管理机制探究
【7月更文挑战第13天】本文深入探讨了操作系统中至关重要的内存管理机制,揭示了其对系统性能与稳定性的影响。通过分析现代操作系统中内存管理的基本原理、关键技术以及面临的挑战,文章提供了对内存分配策略、虚拟内存技术和缓存管理等核心概念的深度解析。进一步地,文章讨论了内存泄漏和碎片化问题,提出了相应的解决策略,旨在为读者提供操作系统内存管理的全面视角,并指出未来可能的发展趋势。
|
1月前
|
缓存 监控 关系型数据库
深入理解Linux操作系统的内存管理机制
【7月更文挑战第11天】在数字时代的浪潮中,Linux操作系统凭借其强大的功能和灵活性,成为了服务器、云计算以及嵌入式系统等领域的首选平台。内存管理作为操作系统的核心组成部分,对于系统的性能和稳定性有着至关重要的影响。本文将深入探讨Linux内存管理的基本原理、关键技术以及性能优化策略,旨在为读者提供一个全面而深入的理解视角,帮助开发者和系统管理员更好地优化和管理Linux系统。
|
1月前
|
机器学习/深度学习 计算机视觉 网络架构
【YOLOv8改进 - 注意力机制】HCF-Net 之 PPA:并行化注意力设计 | 小目标
YOLO目标检测专栏介绍了HCF-Net,一种用于红外小目标检测的深度学习模型,它通过PPA、DASI和MDCR模块提升性能。PPA利用多分支特征提取和注意力机制,DASI实现自适应特征融合,MDCR通过多层深度可分离卷积细化空间特征。HCF-Net在SIRST数据集上表现出色,超越其他方法。论文和代码分别在[arxiv.org](https://arxiv.org/pdf/2403.10778)和[github.com/zhengshuchen/HCFNet](https://github.com/zhengshuchen/HCFNet)上。YOLOv8的PPA类展示了整合注意力机制的结构