《Effective C#》读书笔记——了解.NET内存管理机制<.NET资源管理>

简介:

 我们知道C#是一门虚拟机语言,在C#编译器首先将C#代码编译成IL代码,运行程序时CLR(Common Language Runtime,公共语言运行时)通过调用JIT(just-in-time Compiler即时编译器)来将IL代动态即时编译成可执行的机器码。在CLR中有一个非常重要的概念:CLR GC(Garbage Collector,垃圾收集器),GC自动为我们的应用程序进行内存管理的分配和释放,所以在.NET应用程序中一般很少出现内存泄露、悬挂指针、未初始化指针等问题。同时这也是C#这样的虚拟机语言和C/C++等本地语言最大的不同之处。

  但是GC也不是万能的,我们也需要自己做一些清理工作,管理那些非托管的对象,它们通常是包装操作系统资源的对象,例如,文件句柄、窗口句柄或网络连接

这是因为外部的资源已经超出了运行时(虚拟机)的能力范围,而这些非托管的资源是由操作系统来掌控的。

同时如果使用了事件处理函数和委托在对象之间建立了链接,也可能会造成对象超过你预期的长时间驻留在内存中同时类似于LINQ的这样的在只有请求结果是才真正执行的查询也会导致同样的问题(因为查询将把需要绑定的局部变量使用闭包封装起来,而绑定对象只有在查询结果离开作用域时才会被释放掉)。可以看出在托管环境中开发应用程序,了解CLR内存的管理机制,对于我们写出更富有效率和健壮的代码是非常有益的。

阅读目录

  1.标记并压缩算法(Mark and Compact)

  2.清理非托管资源

      2.1 实现终结器

      2.2 实现IDisposable接口

  3.小节

  进一步阅读和参考

 

1.标记并压缩算法(Mark and Compact)

   GC使用"标记并压缩"(Mark and Compact)算法分析程序运行对象的关系,将不可达部分对象从内存中清理掉。GC会从应用程序的根对象开始,通过对对象树形结构遍历来判定一个对象是否可达,而并不是向COM那样跟踪每个对象的引用。

  我们可以假设有一个EntitySet,是从数据库中读取得到的一系列对象的集合。每个Entity对象都可能包含对其他Entity对象的引用,还可能包含对其他Entity的连接。类似于关系型数据库中的实体集合一样,这些链接和引用也可能是循环的。每个EntitySet中都包含了一系列对象图之间的复杂引用。但是这些并不需要我们担心,GC会替我们很好的完成这个任务。GC通过判断某一个对象(图)是否为垃圾来简化这一操作——当应用程序不需要某一个对象时,将不会保留这个对象的引用。GC会将那些没有被任何对象之间或间接引用的对象判定为垃圾。

  GC在其专门的线程中运行,GC不但自动为程序清理不在使用的内存,还会在每次运行时压缩托管堆。因此内存中空余的空间会是一片连续的内存。

下面图为一个托管堆在一次垃圾收集前后的状态:

我们可以看到GC不但清理不在使用的内存,还会移动内存中的其他对象,以便压缩正在使用的内存,并提高可用的内存空间。

 

2.清理非托管资源

  现在我们知道:托管堆上的内存管理是GC的职责。但是,非托管资源的内存管理则需要由我们开发者自己来负责。.NET为我们提供了两种控制非托管资源生命周期的机制:终结器(finalizer)和IDisposable接口。

 

2.1 (终结器)finalizer

  通过调用Finalize()方法Object.Finalize method, which allows an object to clean up its unmanaged resources properly when the garbage collector reclaims the memory used by the object." style="margin: 0px; padding: 0px;">通知GC回收该对象使用的内存时同时清理其非托管资源。终结器是一种防御性手段,让你的对象不管如何都可以被释放掉其中使用的非托管资源。终结器由GC调用,调用将发生在对象成为垃圾之后的某个时间(我们并不能准确的知道其发生的具体时间,只是知道这将发生在对象不可达之后)。这和C++相比区别很大,有经验的C++开发者往往会在构造函数中分配关键资源,然后在析构函数中释放掉。

2.1.1 使用终结器的问题

  在C#中,终结器迟早都会执行,不过其执行时间却无法预料(也不能)。终结器仅仅能够保证给定类型的对象所分配的非托管资源最终会被释放,但其执行时间确实不可预料的,因此:在我们的代码中最好避免使用终结器,也应该尽量少让代码逻辑使用到终结器

  依赖终结器还对带来性能上的损失。需要执行终结器的对象会给GC带来额外的性能开销。当GC发现某个对象属于垃圾,而该对象又需要执行终结器时,就不能将其直接从内存中移除:首先,GC将调用终结器,而终结器并不在执行垃圾收集的线程上执行。GC将把所有需要执行终结器的对象放在专门的队列中,然后让另一个线程来执行这些对象的终结器。用 Finalize 方法回收对象使用的内存需要至少两次垃圾回收。所有我们知道使用终结器释放非托管资源,资源对象不但会在内存中停留更长时间,GC也需要额外的线程来运行

2.1.2 GC中"代"(Generation)的概念

  .NET的GC为了优化其执行,引入了“代”(Generation)的概念。这概念可以让GC更快速的找到那些更有可能是垃圾的对象。

自上一次垃圾收集以后,新创建的对象属于第0代对象。而如果经过上一次垃圾收集之后仍旧存活的对象,将成为第1代对象。两次以及两次以上的垃圾收集仍没有被销毁的对象就变成了第2代对象。

  这样的分代方式是为了能将局部变量和应用程序生命周期中一直使用的对象分开对待。第0代大多属于局部变量。而成员变量和全局变量则会变成第1代对象,直至变成第2代对象。而GC通过减少检查第1代和第2代的次数来优化执行过程。大概10个周期的GC中,只有一次会同时检查第0代和第1代对象;大概100个周期的GC中,会有一次同时检查所有对象。所有,一个需要终结器的对象可能会比普通的对象多停留9个GC周期。而对于第2代对象,甚至需要100次以上的GC周期才有机会被销毁。

 

2.2 IDisposable接口

   相较于终结器IDisposable接口能够以一种影响更小的方式及时的释放非托管资源。使用IDisposable接口释放非托管资源可以避免执行终结器给GC带来性能上的影响。实现IDisposable接口的 Dispose 方法应该释放它拥有的所有资源。 Dispose method." style="margin: 0px; padding: 0px;">它还应该释放其基类型拥有的所有资源通过调用其父类型的 Dispose 方法。

 

小节:

  GC自动为我们的应用程序进行内存管理的分配和释放,所以在.NET应用程序中一般很少出现内存泄露、悬挂指针、未初始化指针等问题。非托管的资源要使用终结器来保证释放。即使终结器会对程序的性能带来很大的影响,我们也必须提供终结器这是,这是为了保证不出现资源泄露的问题。但是,使用使用IDisposable接口释放非托管资源可以避免执行终结器给GC带来性能上的影响。

 

参考&进一步阅读

MSDN:自动管理内存

MSDN:托管执行过程

MSDN:清理非托管资源

MSDN:深入探讨IDisposable

维基百科:公共语言运行时

《Effective C#》

本文转自gyzhao博客园博客,原文链接:http://www.cnblogs.com/IPrograming/archive/2012/10/22/Effective_CSharp_Unit2.html,如需转载请自行联系原作者
相关文章
|
缓存 并行计算 PyTorch
PyTorch CUDA内存管理优化:深度理解GPU资源分配与缓存机制
本文深入探讨了PyTorch中GPU内存管理的核心机制,特别是CUDA缓存分配器的作用与优化策略。文章分析了常见的“CUDA out of memory”问题及其成因,并通过实际案例(如Llama 1B模型训练)展示了内存分配模式。PyTorch的缓存分配器通过内存池化、延迟释放和碎片化优化等技术,显著提升了内存使用效率,减少了系统调用开销。此外,文章还介绍了高级优化方法,包括混合精度训练、梯度检查点技术及自定义内存分配器配置。这些策略有助于开发者在有限硬件资源下实现更高性能的深度学习模型训练与推理。
2570 0
|
监控 算法 Java
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,特别是垃圾回收(Garbage Collection, GC)机制。我们将从基础概念开始,逐步解析垃圾回收的工作原理、不同类型的垃圾回收器以及它们在实际项目中的应用。通过实际案例,读者将能更好地理解Java应用的性能调优技巧及最佳实践。
396 27
|
存储 监控 算法
Java中的内存管理:理解Garbage Collection机制
本文将深入探讨Java编程语言中的内存管理,着重介绍垃圾回收(Garbage Collection, GC)机制。通过阐述GC的工作原理、常见算法及其在Java中的应用,帮助读者提高程序的性能和稳定性。我们将从基本原理出发,逐步深入到调优实践,为开发者提供一套系统的理解和优化Java应用中内存管理的方法。
|
存储 开发框架 .NET
"揭秘.NET内存奥秘:从CIL深处窥探值类型与引用类型的生死较量,一场关于速度与空间的激情大戏!"
【8月更文挑战第16天】在.NET框架中,通过CIL(公共中间语言)可以深入了解值类型与引用类型的内存分配机制。值类型如`int`和`double`直接在方法调用堆栈上分配,访问迅速,生命周期随栈帧销毁而结束。引用类型如`string`在托管堆上分配,堆栈上仅存储引用,CLR负责垃圾回收,确保高效且自动化的内存管理。
283 6
|
开发框架 监控 .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
462 5
|
存储 运维 Java
💻Java零基础:深入了解Java内存机制
【10月更文挑战第18天】本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
363 1
|
数据库连接 开发者
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第15天】在.NET中,有两种有效的资源释放方式:一是使用`using`语句,适用于实现`IDisposable`接口的对象,如文件流、数据库连接等,能确保资源及时释放,避免泄漏;二是手动调用`Dispose`方法并处理异常,提供更灵活的资源管理方式,适用于复杂场景。这两种方式都能有效管理资源,提高应用性能和稳定性。
512 2
|
算法 Java 数据库连接
.NET 内存管理两种有效的资源释放方式
【10月更文挑战第14天】在 .NET 中,`IDisposable` 接口提供了一种标准机制来释放非托管资源,如文件句柄、数据库连接等。此类资源需手动释放以避免泄漏。实现 `IDisposable` 的类可通过 `Dispose` 方法释放资源。使用 `using` 语句可确保资源自动释放。此外,.NET 的垃圾回收器会自动回收托管对象所占内存,提高程序效率。示例代码展示了如何使用 `MyFileHandler` 类处理文件操作并释放 `FileStream` 资源。
402 2
|
存储 安全 NoSQL
driftingblues9 - 溢出ASLR(内存地址随机化机制)
driftingblues9 - 溢出ASLR(内存地址随机化机制)
289 1

热门文章

最新文章