.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 指向的内存空间,因此在托管堆栈中,连续分配的对象在内存中一定是连续的,这种分配机制非常高效。

相关文章
|
2月前
|
存储 监控 算法
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,着重介绍垃圾回收(Garbage Collection, GC)机制。通过阐述GC的工作原理、常见算法及其在Java中的应用,帮助读者提高程序的性能和稳定性。我们将从基本原理出发,逐步深入到调优实践,为开发者提供一套系统的理解和优化Java应用中内存管理的方法。
|
3月前
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
103 0
|
4月前
|
存储 开发框架 .NET
"揭秘.NET内存奥秘:从CIL深处窥探值类型与引用类型的生死较量,一场关于速度与空间的激情大戏!"
【8月更文挑战第16天】在.NET框架中,通过CIL(公共中间语言)可以深入了解值类型与引用类型的内存分配机制。值类型如`int`和`double`直接在方法调用堆栈上分配,访问迅速,生命周期随栈帧销毁而结束。引用类型如`string`在托管堆上分配,堆栈上仅存储引用,CLR负责垃圾回收,确保高效且自动化的内存管理。
57 6
|
1月前
|
开发框架 监控 .NET
【Azure App Service】部署在App Service上的.NET应用内存消耗不能超过2GB的情况分析
x64 dotnet runtime is not installed on the app service by default. Since we had the app service running in x64, it was proxying the request to a 32 bit dotnet process which was throwing an OutOfMemoryException with requests >100MB. It worked on the IaaS servers because we had the x64 runtime install
|
1月前
|
存储 算法 Java
Go语言的内存管理机制
【10月更文挑战第25天】Go语言的内存管理机制
32 2
|
1月前
|
存储 运维 Java
💻Java零基础:深入了解Java内存机制
【10月更文挑战第18天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
35 1
|
2月前
|
数据库连接 开发者
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第15天】在.NET中,有两种有效的资源释放方式:一是使用`using`语句,适用于实现`IDisposable`接口的对象,如文件流、数据库连接等,能确保资源及时释放,避免泄漏;二是手动调用`Dispose`方法并处理异常,提供更灵活的资源管理方式,适用于复杂场景。这两种方式都能有效管理资源,提高应用性能和稳定性。
|
2月前
|
算法 Java 数据库连接
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第14天】在 .NET 中,`IDisposable` 接口提供了一种标准机制来释放非托管资源,如文件句柄、数据库连接等。此类资源需手动释放以避免泄漏。实现 `IDisposable` 的类可通过 `Dispose` 方法释放资源。使用 `using` 语句可确保资源自动释放。此外,.NET 的垃圾回收器会自动回收托管对象所占内存,提高程序效率。示例代码展示了如何使用 `MyFileHandler` 类处理文件操作并释放 `FileStream` 资源。
|
2月前
|
存储 安全 NoSQL
driftingblues9 - 溢出ASLR(内存地址随机化机制)
driftingblues9 - 溢出ASLR(内存地址随机化机制)
43 1
|
3月前
|
消息中间件
共享内存和信号量的配合机制
【9月更文挑战第16天】本文介绍了进程间通过共享内存通信的机制及其同步保护方法。共享内存可让多个进程像访问本地内存一样进行数据交换,但需解决并发读写问题,通常借助信号量实现同步。文章详细描述了共享内存的创建、映射、解除映射等操作,并展示了如何利用信号量保护共享数据,确保其正确访问。此外,还提供了具体代码示例与步骤说明。