Unity 内存:https://docs.unity3d.com/Manual/performance-memory-overview.html

Unity游戏开发 - 内存篇:https://zhuanlan.zhihu.com/p/533047433

GC:https://en.wikipedia.org/wiki/Tracing_garbage_collection

Unity Boehm GC:https://en.wikipedia.org/wiki/Boehm_garbage_collector

Unity增量GC:https://docs.unity3d.com/Manual/performance-incremental-garbage-collection.html

深入探索垃圾收集:https://blog.csdn.net/yuff100/article/details/137789691

Lua5.3版GC机制理解:https://blog.csdn.net/BigBrick/article/details/85317491

GS GC:http://gsdocs.g-bits.com:3000/#/Reference/Implementation/Doc_GC

CLR GC:https://learn.microsoft.com/zh-cn/dotnet/standard/garbage-collection/fundamentals

为什么要GC

  • 代码创建对象等需要分配内存,对象不需要的时候,就变成内存垃圾,在有限的内存里,在不断分配内存,如果不清理掉内存垃圾,留出空间,会导致撑爆内存,程序崩溃。

  • 通常会指定一个阈值,内存分配达到阈值,就会gc一下清理下空间。

Unity的内存

在这里插入图片描述

GC算法

引用计数法

  • 项目内Unity AB加载用了这种方式。

  • 优点:实现简单,不用的对象可以及时清除。

  • 缺点:无法解决循环引用问题。

    • 对象B引用了对象C,B的计数为1。对象C同时引用了对象B,C的计数为1。实际两对象都不需要的时候也无法回收,因为计数不为0。
  • 实现:给对象加一个计数器,每次对象被一个别的对象引用了,就加1,取消引用就减1。

    • 比如A被B引用,计数为1,A又被C引用,计数为2,当B和C卸载了,A的应用计数为0,这时候就可以清除掉了。

标记-扫描法(Mark-Sweep)

  • Unity(Mono 和 IL2CPP)、CLR、GS 的垃圾回收核心算法都是这种。

  • 优点:算法简单,可以在清理时统一优化内存。

  • 缺点:这个过程需要暂停整个系统,避免这个过程引用变化导致错误回收。表现上就是系统卡住了。
    请添加图片描述

  • 实现分两个阶段:

    1. 标记:把整个“根集”(直接或间接引用托管堆中对象的指针,比如静态字段、局部变量、参数等)进行树状遍历,并将根指向的每个对象标记为 “使用中”。这些对象指向的所有对象等也都会被标记,通过根集可以到达的每个对象都会被标记。

    2. 扫描:所有内存将被从头至尾扫描一遍,检查所有空闲或已使用的区块;未标记为 "未使用 "的区块将无法通过任何根到达,其内存将被释放。对于标记为 "使用中 "的对象,"使用中 "标志会被清除,为下一个循环做准备。

三色标记法

  • Lua 用的这种算法。

  • 优点:每个阶段都可以分开执行,避免卡顿。

  • 缺点:算法复杂,内存开销较高。
    请添加图片描述

  • 实现:

    • 三种颜色代表三种状态:

      • 白色:可回收。

      • 灰色:对象被GC访问过,但这个对象引用的其他对象还没访问过。

      • 黑色:对象被GC访问过,引用的其他对象也被标记了灰或黑。不可回收。

    • 步骤:

      1. 初始都为白色。

      2. 遍历根集,标记为灰色。

      3. 遍历灰色集合,把灰色对象a引用的白色对象也挪到灰色去。把遍历过的对象a挪到黑色集合。

      4. 重复上面步骤,直到灰色集合为空。

      5. 把白色的清理掉。

    • 在gc步骤开始后,如果新增对象,对象将标记为灰色。

内存压缩

  • 可以避免内存碎片,避免出现想要分配大块内存时没有连续空间,也方便分配新的内存。

  • CLR的GC算法中,在标记清理完无引用的对象后,会更新所有对象指针,移动到一起,下次分配内存直接从最后free的指针开始分配。

  • Unity 和 Lua 都不会压缩空间,好处是省去压缩空间导致的额外时间消耗。
    在这里插入图片描述
    在这里插入图片描述

分代 GC

  • 主要减少每次GC检测对象频次。

  • Unity 采用 Boehm GC,不支持分代。

  • Lua GC三色标记法本身可以分阶段处理,没有分代。

CLR 分代

在这里插入图片描述
在这里插入图片描述

  • CLR的分代,简单理解为每次gc存活的对象会留到下一代,减少GC检测的频次,内存不够时再一起GC老一代:

    1. 第0代:这一代是最年轻的,其中包含短生存期对象。 短生存期对象的一个示例是临时变量。 垃圾回收最常发生在此代中。一般对象创建都在这,除了大对象(LOH)(超过85000字节)会直接分配到第2代

    2. 第1代:这一代包含短生存期对象并用作短生存期对象和长生存期对象之间的缓冲区。

    3. 第2代:这一代包含长生存期对象。 长生存期对象的一个示例是服务器应用程序中的一个包含在进程期间处于活动状态的静态数据的对象。

Unity 增量GC

  • 将原本一次性GC分配到多帧处理,减少每次GC时耗时峰值。

  • 写障碍:因为不是一次性GC,如果中间有引用变化,就要缺点是否需要重新扫描对象,会造成额外开销。

  • 如果对象引用变化频繁,标记阶段每次都要到下一次迭代重新扫描,最后还是会变成非增量收集。

Unity Profiler

  • 可以查看触发GC时耗时;和每帧产生托管堆内存大小。

在这里插入图片描述

Logo

这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!

更多推荐