[.NET Core知识点回顾]-自动内存管理

2021年6月28日 50点热度 0条评论

自动内存管理是公共语言运行时在托管执行过程中提供的服务之一。公共语言运行时的垃圾回收器为应用程序管理内存

的分配和释放。对开发人员而言,在开发托管应用程序时不必编写执行内存管理任务代码。

分配内存

初始化新进程时,运行时会为进程保留一个连续的地址空间区域。这个保留的地址空间被称为托管堆。托管堆维护着一个指针,用它指向堆中分配的下一个对象的地址。初始时,该指针设置为指向托管堆的基础地址。托管堆上包含所有的引用类型。应用程序在创建第一个引用类型时,将为托管堆的基址中的类型分配内存。应用程序创建下一个对象时,垃圾回收器在紧接第一个对象后面的地址空间内为它分配内存。只要地址空间可用,垃圾回收器就会继续以这种方式为新对象分配空间。

在托管堆中分配内存要比非托管堆内存分配速度快。由于运行时通过为指针添加值来为对象分配内存,这个速度几乎与堆栈中分配内存一样快。另外由于连续分配的新对象在托管堆中是连续存储,所以应用程序可以快速访问这些对象。

释放内存

垃圾回收器的优化引擎根据所执行的分配决定执行回收的最佳时间。垃圾回收器在执行回收时,会释放应用程序不再使用的对象的内存。它通过检查应用程序的根来确定不再使用的对象。每个应用程序都有一组根。每个根或者引用托管堆中的对象设置为空。应用程序的根包含线程堆栈上的静态字段、局部变量和参数以及CPU寄存器。垃圾回收器可以访问由实时编辑器(JIT)和运行时维护的活动根的列表。垃圾回收器对照此列表检查应用程序的根,并在此过程中创建一个图表,在其中包含所有可从这个根中访问的对象。

不在该图表中的对象无法从应用程序的根中访问。垃圾回收器会考虑无法访问的对象垃圾,并释放为他们分配内存。在回收中,垃圾回收器检查托管堆,查找无法访问对象占据的地址空间块。发现无法访问的对象时,它就使用复制功能来压缩内存中可以访问的对象。释放分配给不可访问对象的地址空间。在压缩了可访问对象的内存后,垃圾回收器就会做出指针更正,一边应用程序的根指向新地址中的对象。它会将托管堆指针定位至最后一个可访问对象以后。

注意:垃圾回收器只要发现大量无法访问的对象时,才会压缩内存。如果托管堆中的所有对象均未被回收,则不需要压缩内存。

为了改进性能,运行时(JIT)为单独堆中的大型对象分配内存。垃圾回收器会自动释放大型对象的内存。为了避免动内存中大型对象,不会压缩此内存。

级别和性能

为了优化垃圾回收器的性能,将托管堆分为三代:第0代,第1代,第2代。运行时的垃圾回收算法基于以下几个普通原理,这些垃圾回收方案的原理已在软件实验中得到验证。首先,压缩托管堆的一部分内存要比压缩整个托管堆速度块。其次,比较新对象生存期比较短,而比较老的对象生存期比较长。最后,比较新的对象趋向于相互关联,并同时由应用程序访问。

运行时的垃圾回收器将新对象存储在第0级中。在应用程序生存期的早期创建对象如果未被回收,则升级并存储在第1级和第2级中。

实际上,垃圾回收器在第0级托管堆已满时执行回收。如果应用程序在第0级托管堆已满时尝试新建对象,垃圾回收器将会发现第0级已没有可分配给对象地址空间。垃圾回收器就执行回收操作。释放第0级托管堆中的地址空间。垃圾回收器从第0级托管堆中的对象开始执行回收。

垃圾回收器执行第0级托管堆的回收后,会压缩可访问对象内存。然后垃圾回收器升级这些对象。并考虑第1级退管堆的这部分。因为未被回收的对象一般为比较长的生存期,所以将他们升级至更高级别。因此垃圾回收器每次回收第0级的托管堆,不会检查第1级和第2级托管堆中的对象。

在执行第0级托管堆的首次回收并把可访问的对象升级至第1级托管堆后,垃圾回收器将考虑第0级托管堆的其余部分。它将继续为第0级的托管堆中的新对象分配内存,直至第0级托管堆无法在分配地址后在执行另外一次的垃圾回收为止。此时,垃圾回收器的优化引擎会决定是否需要检测比较旧的级别中的对象。如:垃圾回收器发现0级托管堆中释放后的内存,不能再创建新的对象,垃圾回收器就会执行第1级托管堆的回收,然后再执行第2级托管堆的回收。如果这样仍不能回收足够的内存,垃圾回收器将执行第2,1和0级托管堆的回收。每次回收后,垃圾回收器都会压缩第0级托管堆中的可访问对象并将他们升级到第1级托管堆。第1级托管堆中未被回收的对象将会升级到第2级托管堆。由于垃圾回收器只支持三个级别,因此第2级托管堆中未被回收的对象继续保留再第2级托管堆中,直到在将来的回收中确定题目为无法访问为止。

为非托管资源释放内存

对于应用程序创建的大多数对象,可以依赖垃圾回收器自动执行必要的内存管理任务。但是,非托管资源需要显示清除。最常用的非托管堆资源类型时包装操作系统资源的对象,如:文件句柄,窗口句柄或网络连接。虽然垃圾回收器可以跟踪封装非托管堆资源的托管对象的生存期,但却无法具体了解如果清理资源。创建封装非托管资源的对象时,建议在公共Dispose方法中提供必要的代码用以清理非托管堆资源。通过提供Dispose方法,对象的用户可以在使用完对象后显示释放内存。使用封装非托管资源的对象时,应在不使用的时候调用Dispose()方法释放。