.Net垃圾回收

2019-03-09  本文已影响0人  JervieQin

原文作如下总结。

简介

.Net垃圾回收器管理应用程序的内存分配和释放。 每当有对象新建时,公共语言运行时都会从托管堆为对象分配内存。 只要托管堆中有地址空间,运行时就会继续为新对象分配空间。 不过,内存并不是无限的。 垃圾回收器最终必须执行垃圾回收来释放一些内存

一些基础

  1. 公共语言运行时 (CLR) 中,垃圾回收器用作自动内存管理器。

  2. 作为一名应用程序开发人员,<u style="box-sizing: border-box;">你只能使用虚拟地址空间,请勿直接操控物理内存</u>。 垃圾回收器为你分配和释放托管堆上的虚拟内存。

    虚拟内存分为

    • 可用(即可分配)

    • 保留(即已分配)

    • 提交(即已指派给物理内存)三种状态。

  3. 发生垃圾回收的三种情况:

    • 物理内存不够了

    • 托管堆上已分配的对象使用的内存过高

    • GC.Collect被调用。

  4. 在垃圾回收器由 CLR 初始化之后,它会分配一段内存用于存储和管理对象。 此内存称为托管堆(与操作系统中的本机堆相对):一个用于小型对象(小型对象堆或 SOH),一个用于大型对象(大型对象堆)。每个托管进程都有一个托管堆。 进程中的所有线程都在同一堆上给对象分配内存。

托管堆视为两个堆的累计:大对象堆和小对象堆。

  1. 垃圾回收器分配的段大小是动态的。 应用程序不应假设特定段的大小或依赖于此大小,也不应尝试配置段分配可用的内存量。

  2. 堆以代数(共3代)来组织,这样就能有效管理短生存对象和长生存对象。垃圾回收最常发生在第0代(包含短生存期对象),当回收第2代时会连带它所有代一起回收。垃圾回收中未回收的对象也称为幸存者,并会被提升到下一代。

  3. 垃圾回收过程中发生了什么:

    • 标记阶段,找到并创建所有活动对象的列表。

    • 重定位阶段,用于更新对将要压缩的对象的引用。

    • 压缩阶段,用于回收由死对象占用的空间,并压缩幸存的对象。

  4. 垃圾回收器使用以下信息来确定对象是否为活动对象

    • 堆栈根。 由实时 (JIT) 编译器和堆栈查看器提供的堆栈变量。 请注意,JIT 优化可以延长或缩短报告给垃圾回收器的堆栈变量内的代码的区域。

    • 垃圾回收句柄。 指向托管对象且可由用户代码或公共语言运行时分配的句柄。

    • 静态数据。 应用程序域中可能引用其他对象的静态对象。 每个应用程序域都会跟踪其静态对象。

      在垃圾回收启动之前,除了触发垃圾回收的线程以外的所有托管线程均会挂起。

大型对象堆(LOH)

如果对象大于或等于 85,000 字节,将被视为大型对象。 此数字根据性能优化确定。 对象分配请求为 85,000 字节或更大时,运行时会将其分配到大型对象堆。

小型对象始终在第 0 代中进行分配,或者根据它们的生存期,可能会提升为第 1 代或第 2 代。 大型对象始终在第 2 代中进行分配。大型对象属于第 2 代,因为只有在第 2 代回收期间才能回收它们。 回收一代时,同时也会回收它前面的所有代。

执行GC的情形

手动清理非托管资源

对于应用创建的大多数对象,可以依赖 .NET 的垃圾回收器来处理内存管理,释放托管资源。 但是,如果创建包括非托管资源的对象,则当你在应用中使用完非托管资源后,必须显式释放这些资源。 最常用的非托管资源类型是包装操作系统资源的对象,如文件、窗口、网络连接或数据库连接。 虽然垃圾回收器可以跟踪封装非托管资源的对象的生存期,但无法了解如何发布并清理这些非托管资源。 如果你的类型使用非托管资源,则应执行以下操作:

  1. 实现释放模式。这就需要你去继承并实现 IDisposable.Dispose方法,来实现对非托管资源的明确性释放。这样当不再使用此对象时,类的使用者可以调用该方法释放资源。

  2. 如果上述情况下对象引用忘记调用 Dispose方法,你得为释放非托管资源做准备。方法有两种:

完成上述内容后,对象引用就可以直接调用 IDisposable.Dispose 实现以释放非托管资源使用的内存。另外值得注意的是, 在正确实现 Dispose 方法后,安全句柄的 Finalize 方法或 Object.Finalize方法的重写会在未调用 Dispose 方法的情况下阻止清理资源。

GC优缺点

在公共语言运行时 (CLR) 中,垃圾回收器用作自动内存管理器。 它提供如下优点:

缺点:

开销影响程序性能。

减少GC开销的策略

性能分析工具(待了解)

上一篇 下一篇

猜你喜欢

热点阅读