谈谈.net对象生命周期(垃圾回收)(2)

  首先,需要明白托管堆不仅仅是一个可由CLR访问的随机内存块。.NET垃圾回收器是堆的“清洁工”,出于优化的目的它会压缩空闲的内存块(当需要时)。为了辅助压缩,托管堆会维护一个指针(通常被叫做下一个对象指针或者是新对象指针),这个指针用来标识下一个对象在堆中分配的地址。

  此外,newobj指令通知CLR来执行下列的核心任务:

  (1)计算要分配的对象所需的全部内存(包括这个类型的数据成员和类型的基类所需的内存)。

  (2)检查托管堆来确保有足够的空间来放置所申请的对象。如果有足够的空间,会调用这个类型的构造函数,构造函数会返回一个指向内存中这个新对象的引用,这个新对象的地址刚好就是下一个对象指针上一次所指向的位置。

  (3)最后,在把引用返回给调用者之前,让下一个对象指针指向托管堆中下一个可用的位置。

  下面的图解释了在托管堆上分配对象的细节。

谈谈.net对象生命周期(垃圾回收)

  在c#中分配对象是一个很频繁的操作,照这样下去托管堆上的空间迟早会被挥霍完,所以,重点来了,如果CLR 发现托管堆没有足够空间分配请求的类型时,它会执行一次垃圾回收来释放内存

  当执行垃圾回收时,垃圾收集器临时挂起当前进程中的所有的活动线程来保证在回收过程中应用程序不会访问到堆。(一个线程是一个正在执行的程序中的执行路径)。一旦垃圾回收完成,挂起的线程又可以继续执行了。还好,.NET 垃圾回收器是高度优化过的,所以用户很少能察觉到应用程序中的短暂中断。

  通过对CIL的new指令作用的解读,我们知道了:如果托管堆没有足够的空间分配一个请求的对象,则会执行一次垃圾回收。

(讲到这里c#程序员停了下来,喝了口保温杯里的枸杞红枣大补茶🍵,清了清嗓子,继续开始解惑...)

应用程序根的作用 — 区分不可到达的对象

  现在让我们来讨论一下垃圾回收器怎样确定什么时候“不再需要”一个对象。为了理解细节,你需要知道应用程序根的概念。

  简单来说,一个根是一个引用,这个引用指向堆上面的一个对象的。严格来说,一个根可以有以下几种情况:

  (1) 指向全局对象的引用(尽管C#不支持,但CIL代码允许分配全局对象)

  (2)指向任何静态对象

  (3)指向一个应用程序代码中的局部对象

  (4)指向传入到一个函数中的对象参数

  (5)指向等待被终结(finalized)的对象

  (6)任何一个指向对象的CPU寄存器

  在一次垃圾回收的过程中,运行环境会检查托管堆上面的对象是否仍然是从应用程序根可到达的。为了检查可达,CLR会建立一个代表堆上每个可达对象的图。对象图用来记录所有可达的对象。同时,注意垃圾回收器绝不会在图上标记一个对象两次,因此避免了烦人的循环引用。

  假设托管堆上有名字为A,B,C,D,E,F和G的对象集合。在一次垃圾回收过程中,会检查这些对象(同时包括这些对象可能包含的内部对象引用)是否是根可达的。一旦图被建立起来,不可达的对象(在此是对象C和F)被标记为垃圾。

  下图是上述场景的一个可能的对象图(你可以把箭头读作依赖或者需要,例如"E依赖于G,间接依赖于B,“A不依赖任何对象”等)。

谈谈.net对象生命周期(垃圾回收)

(创建的对象图是用来决定哪些对象是应用程序根可达的。)

  一旦一个对象已经被标记为终结(此例子中是C和F--在图中没有他俩),它在内存中就被清理掉了。在此时,堆上的剩余内存空间被压缩,这会导致CLR修改活动的应用程序根集合(和对应的指针)来指向正确的内存位置(这个操作是自动透明的)。最后,调整下一个对象指针来指向下一个可用的内存位置。

  下图阐明了清除和压缩堆的过程。

谈谈.net对象生命周期(垃圾回收)

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wdsydg.html