了解.NET中的垃圾回收

原文来自互联网,由长沙DotNET技术社区编译。尽管这是一篇来自2009年的古老的文章,但或许能够对你理解GC产生一些作用。 

了解.NET中的垃圾回收

一旦了解了.NET的垃圾收集器是如何工作的,那么可能会触及.NET应用程序的一些更为神秘的问题时,进行原因分析就会变得更加清楚。NET已经不在提供显式内存管理的方式,但在开发.NET应用程序时,仍然有必要分析内存的使用情况,以便避免与内存相关的错误和某些性能问题。

.NET的垃圾收集器已在Windows应用程序中作为显式内存管理和内存泄漏的结束而开放给我们:这个想法是,在后台运行垃圾收集器的情况下,开发人员不再需要担心管理它们创建的对象的生命周期–应用程序完成处理后,垃圾收集器将对其进行处理。

但是,实际情况要复杂得多。垃圾收集器无疑解决了非托管程序中最常见的泄漏-由开发人员在完成使用后忘记释放内存而引起的泄漏。它还解决了内存释放过早的相关问题,但是当垃圾收集器对开发人员对对象是否仍然处于“活动状态”并且能够进行开发时有不同的看法时,解决该问题的方式可能导致内存泄漏。要使用的。解决这些问题之前,您需要对收集器的工作方式有所了解。

垃圾收集器如何工作

那么,垃圾收集器如何实现其魔力?基本思想非常简单:它检查对象在内存中的布局方式,并通过遵循一系列引用来标识正在运行的程序可以“访问”的所有那些对象。

当垃圾回收开始时,它将查看一组称为“ GC根”的引用。这些是由于某种原因总是可以访问的内存位置,并且包含对程序创建的对象的引用。它将这些对象标记为“活动”,然后查看它们引用的所有对象。它也将这些标记为“实时”。它以这种方式继续,遍历它知道是“活动”的所有对象。它将它们引用的所有内容都标记为也被使用,直到找不到其他对象为止。

如果某个对象或其超类之一的字段包含另一个对象,则该对象由垃圾收集器标识为引用另一个对象。

一旦知道了所有这些活动对象,就可以丢弃所有剩余的对象,并将空间重新用于新对象。.NET压缩内存,以确保没有间隙(有效地压缩丢弃的对象不存在)–这意味着空闲内存始终位于堆的末尾,并可以非常快速地分配新对象。

GC根本身不是对象,而是对对象的引用。GC根引用的任何对象将自动在下一个垃圾回收中保留下来。.NET中有四种主要的根:

当前正在运行的方法中的局部变量被视为GC根。这些变量引用的对象始终可以通过声明它们的方法立即访问,因此必须保留它们。这些根的生命周期可以取决于程序的构建方式。在调试版本中,局部变量的持续时间与方法在堆栈上的时间一样长。在发行版本中,JIT能够查看程序结构以找出执行过程中该方法可以使用变量的最后一点,并在不再需要该变量时将其丢弃。这种策略并不总是使用,可以通过例如在调试器中运行程序来关闭。

静态变量也始终被视为GC根。声明它们的类可以随时访问它们引用的对象(如果是公共的,则可以访问程序的其余部分),因此.NET将始终保持它们不变。声明为“线程静态”的变量仅会在该线程运行时持续存在。

如果通过互操作将托管对象传递给非托管COM +库,则该对象也将成为具有引用计数的GC根。这是因为COM +不进行垃圾收集:它使用引用计数系统;通过将引用计数设置为0,一旦COM +库完成了该对象,它将不再是GC根目录,并且可以再次收集。

如果对象具有终结器,则在垃圾回收器确定该对象不再“处于活动状态”时,不会立即将其删除。相反,它成为一种特殊的根,直到.NET调用了finalizer方法。这意味着这些对象通常需要从内存中删除一个以上的垃圾回收,因为它们在第一次发现未使用时仍将生存。

对象图

总体而言,.NET中的内存形成了一个复杂的,打结的引用和交叉引用图。这可能使得很难确定特定对象使用的内存量。例如,List对象使用的内存非常小,因为List 类只有几个字段。但是,其中之一是列表中的对象数组:如果列表中有许多条目,则这可能会很大。这几乎总是由列表“独占”,因此关系非常简单:列表的总大小是小的初始对象和它引用的大数组的大小。但是,数组中的对象可能完全是另一回事:很可能存在通过内存的其他路径来访问它们。在这种情况下,

当循环引用开始起作用时,事情变得更加混乱。

 

在开发代码时,通常将内存视为组织为更容易理解的结构:从各个根开始的树:

 

确实,以这种方式进行思考确实使(更确实可能)思考对象在内存中的布局方式。这也是编写程序或使用调试器时表示数据的方式,但这很容易忘记一个对象可以附加到多个根。这通常是.NET中内存泄漏的来源:开发人员忘记或从未意识到,一个对象锚定到多个根。考虑一下此处所示的情况:将GC root 2设置为null实际上不会允许垃圾收集器删除任何对象,这可以从查看完整图形中看到,而不能从树中看到。

内存剖析器可以从另一个角度查看图形,就像树根植于单个对象并向后跟随引用以将GC根放在叶子上一样。对于根2引用的ClassC对象,我们可以向后跟随引用以获取下图:

 

通过这种方式的思考表明,ClassC对象具有两个最终的“所有者”,在垃圾收集器将其删除之前,这两个对象都必须放弃它。一旦将GC根目录2设置为null,就可以断开GC根目录3与该对象之间的任何链接,以便将其删除。

在实际的.NET应用程序中,这种情况很容易出现。最常见的是,数据对象被用户界面中的元素引用,但在数据处理完毕后不会被删除。这种情况并不是很泄漏:当用新数据更新UI控件时,将回收内存,但是这可能意味着应用程序使用的内存比预期的要多得多。事件处理程序是另一个常见原因:很容易忘记一个对象的寿命至少与它从中接收事件的对象一样长,对于某些全局事件处理程序(如Application类中的事件),这种情况永远存在。

实际的应用程序,尤其是那些具有用户界面组件的应用程序,具有比这复杂得多的图形。甚至可以从大量不同的地方引用对话框中的标签之类的简单内容…

 

很容易看到偶然的物体如何在迷宫中丢失。

垃圾收集器的局限性

仍在引用的未使用对象

.NET中垃圾收集器的最大局限性是一个细微的限制:虽然它可以检测和删除未使用的对象,但实际上它会找到未引用的对象。这是一个重要的区别:程序可能永远不会再引用对象。但是,尽管有一些路径导致它可能仍被使用,但它永远不会从内存中释放出来。这导致内存泄漏;在.NET中,当将不再使用的对象保持引用状态时,会发生这些情况。

尽管内存使用率上升的症状很明显,但这些泄漏的来源可能很难发现。有必要确定哪些未使用的对象保留在内存中,然后跟踪引用以找出为什么不收集它们。内存分析器对于此任务至关重要:通过比较发生泄漏时的内存状态,可以找到麻烦的未使用对象,但是没有调试器可以向后跟踪对象引用。

垃圾收集器旨在处理大量资源,也就是说,释放对象的位置无关紧要。在现代系统上,内存属于这一类(何时回收内存无关紧要,只要及时完成以防止新分配失败)。仍然有一些资源不属于此类:例如,需要快速关闭文件句柄以避免引起应用程序之间的共享冲突。这些资源不能由垃圾收集器完全管理,因此.NET为管理这些资源的对象提供Dispose()方法以及using()构造。在这些情况下,对象的稀缺资源可通过实施Dispose 方法,但是紧要的内存要少得多,然后由垃圾回收器释放。

Dispose意味着.NET没有什么特别的,因此仍必须取消引用已处置的对象。这使已处置但尚未回收的对象成为内存泄漏源的良好候选对象。

堆的碎片

.NET中一个鲜为人知的限制是大对象堆的限制。成为该堆一部分的对象不会在运行时移动,这可能导致程序过早地耗尽内存。当某些对象的寿命比其他对象长时,这将导致堆在对象过去所在的位置形成孔-这称为碎片。当程序要求一个大的内存块,但堆变得非常分散,以至于没有单个内存区域足以容纳它时,就会发生问题。内存分析器可以估计程序可以分配的最大对象:如果该对象正在下降,则很可能是原因。一个OutOfMemoryException当程序显然具有大量可用内存时,通常会发生由碎片引起的错误–在32位系统上,进程应至少能够使用1.5Gb,但是由于碎片导致的故障通常会在使用该碎片之前开始发生很多内存。

碎片化的另一个征兆是.NET通常必须保留分配给应用程序的空洞所使用的内存。这显然导致它使用比在任务管理器中查看所需的内存更多的内存。这种效果通常相对来说是无害的:Windows非常擅长于意识到未被占用的孔所占用的内存并将其分页,并且如果碎片没有恶化,则程序将不会耗尽内存。但是,对于用户而言,这看起来并不好,他们可能会认为该应用程序浪费且“ blo肿”。当探查器显示程序分配的对象仅使用少量内存,而任务管理器显示该进程占用大量空间时,通常会发生这种情况。

垃圾收集器的性能

在性能方面,垃圾收集系统的最重要特征是垃圾收集器可以随时开始执行。这使它们不适用于定时至关重要的情况,因为任何操作的定时都可能被收集器的操作所抛弃。

.NET收集器有两种主要的操作模式:并发和同步(有时称为工作站和服务器)。默认情况下,并发垃圾收集用于桌面应用程序,同步用于服务器应用程序(例如ASP.NET)。

在并发模式下,.NET将尝试避免在进行收集时停止正在运行的程序。这意味着在给定的时间内应用程序可以完成的总次数较少,但应用程序不会暂停。这对交互式应用程序很有用,在交互应用程序中,给用户留下印象,即应用程序应立即做出响应,这一点很重要。

在同步模式下,.NET将在垃圾收集器运行时挂起正在运行的应用程序。实际上,这总体上比并发模式更有效–垃圾回收花费相同的时间,但是不必与程序继续运行进行竞争–但是,这意味着必须执行完整的回收时会有明显的暂停。。

如果默认设置不合适,则可以在应用程序的配置文件中设置垃圾收集器的类型。当更重要的是应用程序具有高吞吐量而不是显示响应时,选择同步收集器可能很有用。

在大型应用程序中,垃圾收集器需要处理的对象数量会变得非常大,这意味着访问和重新排列所有对象都将花费很长时间。为了解决这个问题,.NET使用了“分代”垃圾收集器,该垃圾收集器试图将优先级赋予较小的一组对象。这个想法是,最近创建的对象更有可能被快速释放,因此,当试图释放内存时,分代垃圾收集器会优先处理它们,因此.NET首先查看自上一次垃圾收集以来已分配的对象,并且只会开始如果无法通过这种方式释放足够的空间,请考虑使用较旧的对象。

如果.NET可以自行选择收集时间,则此系统效果最佳,并且如果GC.Collect调用()会中断该系统,因为这通常会导致新对象过早地变旧,这增加了在不久的将来再次进行昂贵的完整收集的可能性。

具有终结器的类也会破坏垃圾收集器的平稳运行。这些类的对象不能立即删除:相反,它们进入终结器队列,并在运行终结器后从内存中删除。这意味着它们所引用的任何对象(以及那些对象所引用的任何对象,依此类推)至少也必须在此之前保留在内存中,并且在内存再次可用之前需要两次垃圾回收。如果该图包含带有终结器的许多对象,则这可能意味着垃圾收集器需要多次通过才能完全释放所有未引用的对象。

有一个避免此问题的简单方法:IDisposable在可终结类上实现,将完成对象所需的操作移到Dispose()方法中并GC.SuppressFinalize()在最后调用。然后可以修改终结器以调用该Dispose()方法。GC.SuppressFinalize()告诉垃圾回收器,该对象不再需要终结,可以立即被垃圾回收,这可以导致更快地回收内存。

结论

如果您花一些时间了解垃圾收集器的工作方式,则更容易理解应用程序中的内存和性能问题。它表明,尽管.NET减轻了内存管理的负担,但并不能完全消除跟踪和管理资源的需求。但是,使用内存分析器来诊断和修复.NET中的问题更加容易。考虑到.NET在开发中尽早管理内存的方式可以帮助减少问题,但是即使那样,由于框架或第三方库的复杂性,此类问题仍然可能出现。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/310528.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

数据结构与算法--数组:二维数组中查找

数组 数组最简单的是数据结构,占据一整块连续的内存并按照顺序存储数据,创建数组时候,我们需要首先指定数组的容量大小,然后根据大小分配内存。即使我们只在数组中存储一个元素,亚需要为所有数据预先分配内存&#xf…

数据结构与算法--字符串:字符串替换

数据结构与算法–字符串:字符串替换 字符串的优化 由于字符串在编程时候使用的评率非常高,为了优化,很多语言都对字符串做了特殊的规定。下面我们讨论java中字符串的特性java中的字符数组以’\0’ 结尾,我们可以利用这个特性来找…

数据结构与算法--经典10大排序算法(动图演示)【建议收藏】

十大经典排序算法总结(动图演示) 算法分类 十大常见排序算法可分为两大类: 比较排序算法:通过比较来决定元素的位置,由于时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序非比较类型排序&…

如何查找,修复和避免C#.NET中内存泄漏的8个最佳实践

原文来自互联网,由长沙DotNET技术社区编译。本文来源:https://michaelscodingspot.com/find-fix-and-avoid-memory-leaks-in-c-net-8-best-practices/从事大型企业项目的任何人都知道内存泄漏就像是大型酒店中的老鼠。当它们很少时,您可能不会…

ASP.NET Core技术研究-探秘依赖注入框架

ASP.NET Core在底层内置了一个依赖注入框架,通过依赖注入的方式注册服务、提供服务。依赖注入不仅服务于ASP.NET Core自身,同时也是应用程序的服务提供者。毫不夸张的说,ASP.NET Core通过依赖注入实现了各种服务对象的注册和创建,…

Redis遍历方式思考--字典扩容方式

全量遍历keys 工作中线上Redis维护,有时候我们需要查询特定前缀的缓存key列表来手动处理数据。可能是修改值,删除key。那么怎么才能快速的从海量的key中查找到对应的前缀匹配项。Redis提供了一下简单的指令,例如keys用来满足特定正则下的key…

从项目到产品: 软件时代需要价值流架构师 | IDCF

译者:无敌哥原文地址: https://thenewstack.io/the-age-of-software-needs-value-stream-architects/ 本文翻译仅供学习交流之用。原文作者 Mik Kersten 出版了《Project to Product》本系列共四篇文章,分别是01 从项目到产品:软件需要从物理…

Redis高效性探索--线程IO模型,通信协议

Redis线程IO模型 Redis是单线程,这个毋庸置疑Redis单线程能做到这么高的效率?不用怀疑,还有很多其他的服务都是单线程但是也有超高的效率,比如Node.js,Nginx也是单线程。Redis单线程高效原因: Redis所有数…

Redis持久化-深入理解AOF,RDB

持久化 Redis数据全部在内存中,如果宕机,数据必然丢失,因此必须有一种机制保证Redis数据不会因为故障丢失,这就是Redis的持久化机制持久化方式两种:AOF,RDB,如下图 RDB快照模式是一次全量备份&…

推荐一个集录屏、截图、音频于一体的软件给大家

捕获屏幕,网络摄像头,音频,光标,鼠标单击和击键GitHub:https://github.com/MathewSachin/Captura特性 免费 100%免费,你不需要花一分钱开源 根据MIT许可的条款,可以在Github上获得Captura的源…

Redis高效性探索--管道

管道 开始接触Redis时候,对应Redis管道有一个错误认识,任务是redis服务器提供的一种特别的技术,有了这种技术可以加速Redis的存取效率,但是实际上Redis的管道计算(Pipeline)本身是客户端提供的技术&#x…

Redis--事务理解

事务 一个成熟的数据库系统一般都会有事务的支持,Redis作为一个缓存数据库也不例外,Redis的事务比之关系型数据库mysql,oracle等算比较简单的,Redis中无需理解那么多事务模型,可以直接使用。不过也正是因为简单&#…

.NET中的内存管理

原文来自互联网,由长沙DotNET技术社区编译。 .NET中的内存管理资源分配Microsoft .NET公共语言运行时要求从托管堆分配所有资源。当应用程序不再需要对象时,它们将自动释放。初始化进程后,运行时将保留地址空间的连续区域,该区域最…

Redis存储优化--小对象压缩

小对象压缩 Redis是一种内存数据库,内存是计算机中一种比较宝贵的资源,如果我们不注意节约,Redis很可能出现内存不足,最终导致崩溃。Redis为了优化数据结构的内存占用,增加了非常多的优化点,这些优化也是牺…

.Net微服务实战之技术架构分层篇

一拍即合上一篇《.Net微服务实战之技术选型篇》,从技术选型角度讲解了微服务实施的中间件的选择与协作,工欲善其事,必先利其器,中间件的选择是作为微服务的基础与开始,也希望给一直想在.Net入门微服务的同行有一个很好…

Redis高可用基石--主从同步

主从同步 当我们将Redis用于线上环境,单机肯定是不行的,即使不做集群,我们也应该做主从,有了主从,当主节点(master)挂掉时候,让运维将从节点(slave)接管&…

.NET 下基于动态代理的 AOP 框架实现揭秘

.NET 下基于动态代理的 AOP 框架实现揭秘Intro之前基于 Roslyn 实现了一个简单的条件解析引擎,想了解的可以看这篇文章 基于 Roslyn 实现一个简单的条件解析引擎执行过程中会根据条件的不同会在运行时创建一个类,每一次创建都会生成一个新的程序集&#…

C++实现链式基数排序

代码如下: #include <iostream> #include <cmath> using namespace std; typedef int KeyType; const int END -1; const int Radix 10;typedef struct Node {KeyType key;struct Node *next; };Node *CreateList() {KeyType x;Node *q nullptr;cin >> x…

Blazor WebAssembly 3.2.0 Preview 4 如期发布

ASP.NET团队如期3.16在官方博客发布了 Blazor WebAssembly 3.2.0 Preview 4&#xff1a;https://devblogs.microsoft.com/aspnet/blazor-webassembly-3-2-0-preview-4-release-now-available/ &#xff0c;同时在twitter上发了一条信息带上了下面这张图&#xff0c;这张图很形象…

C#/.Net Core/WPF框架初建(国际化、主题色)

English | 简体中文作为 TerminalMACS 的一个子进程模块 - WPF管理端&#xff0c;目前搭建框架部分功能&#xff1a;本地化、国际化、主题色修改等。导航目录1.框架已添加功能说明1.1. 国际化、本地化1.2. Metro风格主窗体1.3. 动态更换主题色2.关于TerminalMACS及本WPF管理端 …