Unity使用的GC方式——贝姆GC(BOEHM GC)

这篇具有很好参考价值的文章主要介绍了Unity使用的GC方式——贝姆GC(BOEHM GC)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

Unity合作的Mono版本为Mono的早期版本,此时还没有使用SGen GC,后来Mono将默认GC方式改为SGen GC,Unity并没有继续购买,因此Unity使用的GC方式仍然是老的贝姆GC。
贝姆GC官方网页:https://www.hboehm.info/gc/index.html

1. 阶段

贝姆GC是一种基于标记清除法的GC方式。其整体过程可粗略分为四个阶段:

  1. 准备阶段:所有对象的MarkBit重置。
  2. 标记阶段:从Root出发进行扫描,将可达对象进行标记。
  3. 清理阶段:扫描托管堆,将所有未标记的对象返回给对应的FreeList。
  4. Finalization阶段:所有注册了终结器的无效对象加入终结器队列单独处理。

2. 分配内存

贝姆GC对于对象的内存管理是区分内存类型和内存大小的。

  • 内存类型分为NORMAL、PTRFREE和UNCOLLECTABLE三类(这个UNCOLLECTABLE是从网上其他文章看来的,原文里没发现,只看到STUBBORN,不过这个并不重要)。
  • 根据对象占用内存大小不同,又分为大对象和小对象。

HBLK

运行时从操作系统申请的堆内存被划分成一个一个的内存块进行管理,称为HBLK。HBLK的大小不同,其粒度为内存页大小的整数倍,并通过数组记录HBLK链表,数组下标(0除外)代表当前位置链表中HBLK管理的相邻Page的数量。比如数组下标为1的位置,其链表内每个HBLK大小为1个Page,下标为4的位置,HBLK的大小则为4个Page。
贝姆gc,垃圾回收(GC),unity,游戏引擎,GC,内存管理,c#

大对象存储

对于大对象的分配,将大对象的内存大小向上计算,得到满足大小的HBLK粒度,然后从对应粒度的HBLK链表中找到可用的HBLK进行存储。

小对象存储

对于小对象的分配,由于每个对象实际占用的内存大小各不相同,为了避免内存被划分的稀碎,贝姆GC对于小对象的内存分配也是分粒度的,一般以16B作为基数。

小对象并不是直接存储在HBLK中,而是将HBLK中的Page根据指定的小对象的内存粒度进行拆分,这样就形成了一小块一小块不同粒度的内存块,将同样粒度的内存块串成链表,就有了不同粒度的可用内存链表(ok_freeList)。另外,上文说到,GC回收的内存也会被返还给FreeList。

当申请创建小对象时,根据对象所占内存向上计算所属的内存粒度,然后查找对应粒度的FreeList,找到一块可用的内存,将对象存储进去,然后将该块内存从FreeList移除。

当对应粒度的FreeList为空时,会触发一次GC,尝试回收内存块,如果还没有可用的内存块,则查找HBLK链表,找到一块可用的HBLK,将HBLK中的Page拆分,补充FreeList。

这里需要注意的点是,由于对象内存分了类型,所以不同类型的对象不能存放到一起,因此是每个类型的内存都维持了一个数组,里面记录着可分配给当前类型的不同粒度的内存块的链表。而向HBLK申请补充FreeList时也一样,一旦HBLK对某一类型和粒度的对象进行了拆分,这一整个HBLK就不能再用于存储其他类型和粒度的对象了。

由此我们可以得出,在Unity中,创建均匀且大量使用的小对象对于内存是更友好的。而创建尺寸各异且数量很少的对象就会导致HBLK被拆分后实际又没有那么多对象可存,从而浪费内存。

贝姆gc,垃圾回收(GC),unity,游戏引擎,GC,内存管理,c#

3. 标记阶段

标记过程为STW的,通过标记栈,依次从Uncollectable对象和ROOT对象出发进行遍历,将所有可被遍历到的对象进行标记。当标记栈被清空时,标记阶段结束,此时未被标记的对象被认为是可回收的。
其中,ROOT对象包括以下三类:

  • 寄存器内的对象
  • 栈上的对象
  • 静态区的对象

4. 清理阶段

原文中提到,清理阶段实际上并不是一个真正的独立阶段。因为这个阶段并不是真的停下来什么都不做开始清理内存,其对于内存的处理分为几种情况。

  • 对于未标记的大对象,由于其存储在HBLK中,直接将其所占用的内存返还给HBLK FreeList。
  • 对于存储小对象的Page,如果其MarkBit Table中都是可被清理,则整页返还给HBLK FreeList。
  • 对于有小对象不可清除的Page,暂时不做处理,等到出现上文分配内存需要查找可用FreeList时,再对要分配的类型和粒度的Page进行检查,将其中可以回收的内存块返还给ok_FreeList。

这样做的好处是当分配内存操作触发GC后,不需要立刻将所有内存都进行回收,只需要对一部分当前要分配的类型和粒度的内存进行回收即可。

5. Finalization 阶段

我们知道,运行时管理的是托管对象,假设现在有一个托管对象A,且A引用了非托管对象B。为了避免内存泄漏,就需要在A被释放之前先手动释放对B的引用。我们固然可以在特定的逻辑节点进行这个操作,但更多的时候,我们希望在A被回收前自动结束对B的引用。

从上文可以知道,A的回收是在清理阶段或者在新分配内存时由GC线程自己触发的,我们并不清楚这个操作发生的具体时间点。为了能够使这个操作可控,于是有了终结器机制。

所有注册了终结器的对象(比如重写了Finalize方法)会被额外存到一个单独的哈希表中,我们可以称之为FinalizableList。意味着列表中的所有对象在被回收前都需要先执行终结器进行一些额外的操作。

那是不是说对于这些对象,只要简单地先执行终结器再释放就可以了呢?当然也不是。因为终结器中到底写了些什么逻辑运行时是不知道的,所以理论上就存在这样的情况:一个本来不可达的对象(未被标记的垃圾内存)在终结器中手动添加了从某个可达对象到自己引用,于是在终结器执行结束之后自己就变成非垃圾对象,也就是“复活”了。同样,该对象引用链之下的所有对象也都跟着复活了。

基于此,在标记阶段结束时,会检查FinalizableList列表,将其中所有不可达的对象重新推到标记栈上,然后由其出发进行遍历,重新进行一轮标记。也就是先默认这些对象复活了。

这里需要注意的是,该阶段的本意是先保证这些Finalizable对象下游的对象不会被提前回收造成程序错误,然后对其执行终结器。然而一个Finalizable对象A可能在重新标记时遍历到另一个Finalizable对象B,这时,如果A和B都执行终结器被认为是不安全的,于是在遍历出发时,对象本身并不会被标记,只有在遍历过程中被其他对象引用到时才会进行标记。

在这一轮标记结束后,将仍然未被标记过的Finalizable对象从FinalizableList移除,加入一个等待执行终结器的队列F-Queue。本轮GC便不再对这些对象进行回收。

F-Queue中的对象会在适当的时机执行终结器并从队列中移除。这样在之后的GC过程中,如果该对象仍未被标记到,由于它既不在FinalizableList中也不在F-Queue中,就可以正常被释放了,而且终结器也不会被重复执行。

由上可知,即使没有手动复活的操作,注册了终结器的对象最少也要经过两轮GC才能被真正释放,而那些被Finalizable对象直接或间接引用到的Finalizable对象甚至需要更多轮的GC才会被释放,所以才会有非必要不要随便注册终结器的说法。文章来源地址https://www.toymoban.com/news/detail-838389.html

到了这里,关于Unity使用的GC方式——贝姆GC(BOEHM GC)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • JVM:垃圾回收机制(GC)

    引用计数算法:         在对象中添加一个引用计数器,当每有一个地方引用它时,计数器值加一。当引用失效时,计数器值就减一。当一个对象的计数器为零时,表示该对象没有被任何其他对象引用,因此可以被释放。 优点 :是可以及时回收垃圾对象,避免内存泄漏,且

    2024年01月19日
    浏览(49)
  • JVM----GC(垃圾回收)详解

    Automatic Garbage Collection (自动垃圾回收)是JVM的一个特性,JVM会启动相关的线程,该线程会轮训检查heap memeory,并确定哪些是未被引用的(unreferenced),即未被使用的;哪些是被引用的(referenced),即正在使用的。 在C/C++语言中,对象内存的分配与回收,是手动进行分配与回收

    2024年02月09日
    浏览(47)
  • java---垃圾回收算法(GC)

    目录 一、如何判断一个对象是否存活 1.引用计数法 2.可达性分析法 二、垃圾回收算法 1.标记清除法 2.复制算法 3.标记整理法 4.分代算法 具体流程 注意事项 空间分配担保原则 总结 Java 堆中存放着几乎所有的对象实例,垃圾回收器在对堆进行垃圾回收前,首先要判断这些对象

    2024年02月05日
    浏览(37)
  • 02JVM_垃圾回收GC

    在 堆 里面存放着java的所有对象实例,当对象为“死去”,也就是不再使用的对象,就会进行垃圾回收GC 1.1引用计数器 介绍 在对象中添加一个引用计数器,当一个对象被其他变量引用时这个对象的引用计数器加1。当某个变量不再引用这个对象时引用计数器减1。当这个引用计

    2024年02月09日
    浏览(40)
  • 【JVM】垃圾回收机制详解(GC)

    可以看jvm详解之后,再来理解这篇文章更好 堆和方法区,主要发生在堆中,然后主要发生在堆的伊甸园区(Eden)。 Java中的垃圾回收是根据 可达性分析算法(Reachability Analysis) 和 引用计数算法 来判断对象是否存活的。 JDK.1.2 之后,Java 对引用的概念进行了扩充,将引用分为了:

    2024年02月13日
    浏览(55)
  • 深入学习JVM —— GC垃圾回收机制

            前面荔枝已经梳理了有关JVM的体系结构和类加载机制,也详细地介绍了JVM在类加载时的双亲委派模型,而在这篇文章中荔枝将会比较详细地梳理有关JVM学习的另一大重点——GC垃圾回收机制的相关知识,重点了解的比如对象可达性的判断、四种回收算法、分代回收

    2024年02月14日
    浏览(48)
  • .NET CLR之垃圾回收(GC)

    公共语言运行库 (common language runtime,CLR) 是托管代码执行核心中的引擎。 运行库为托管代码提供各种服务,如跨语言集成、代码访问安全性、对象生存期管理、调试和分析支持。 它是整个.NET框架的核心,它为.NET应用程序提供了一个托管的代码执行环境。 它实际上是驻留在内

    2024年02月11日
    浏览(34)
  • 【Java】图解 JVM 垃圾回收(一):GC 判断策略、引用类型、垃圾回收算法

    垃圾 是指运行程序中 没有任何引用指向的对象 ,需要被回收。 内存溢出 :经过垃圾回收之后,内存仍旧无法存储新创建的对象,内存不够溢出。 内存泄漏 :又叫 “ 存储泄漏 ”,对象不会再被程序使用了,但是 GC 又不能回收它们。例如:IO 流不适用了但是没有被 Close、

    2024年02月19日
    浏览(50)
  • 【JAVAEE】JVM中垃圾回收机制 GC

      博主简介:想进大厂的打工人 博主主页: @xyk: 所属专栏: JavaEE初阶   上篇文章我们讲了java运行时内存的各个区域。 传送门:【JavaEE】JVM的组成及类加载过程_xyk:的博客-CSDN博客 对于程序计数器、虚拟机栈、本地方法栈这三部分区域而言,其生命周期与相关线程有关,随线

    2024年02月16日
    浏览(45)
  • 深入理解GO语言——GC垃圾回收二

    书接上回,无论怎么优化,Go V1.3都面临这个一个重要问题,就是mark-and-sweep 算法会暂停整个程序 。 Go是如何面对并这个问题的呢?接下来G V1.5版本 就用 三色并发标记法 来优化这个问题 Golang中的垃圾回收主要应用三色标记法,GC过程和其他用户goroutine可并发运行,但需要一

    2024年04月11日
    浏览(37)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包