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。
在这里插入图片描述

大对象存储

对于大对象的分配,将大对象的内存大小向上计算,得到满足大小的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被拆分后实际又没有那么多对象可存,从而浪费内存。

在这里插入图片描述

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才会被释放,所以才会有非必要不要随便注册终结器的说法。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://xiahunao.cn/news/2808459.html

如若内容造成侵权/违法违规/事实不符,请联系瞎胡闹网进行投诉反馈,一经查实,立即删除!

相关文章

15.4K Star,超强在线编辑器

Hi,骚年,我是大 G,公众号「GitHub指北」会推荐 GitHub 上有趣有用的项目,一分钟 get 一个优秀的开源项目,挖掘开源的价值,欢迎关注。 今天推荐一款非常棒的开源实时协作编辑器,可用于多人同时编…

SQLServer 格式化数据的方法

格式化数据一般考虑使用FORMAT 或者CONVERT ​​​​​​​函数,FORMAT 函数是在 SQL Server 2012 中引入的,如果你使用的是较早版本的 SQL Server,则可能需要考虑使用其他方法,如 CONVERT 函数。 在 SQL Server 中,FO…

【C++ QT项目5】——基于HTTP与JSON数据流的天气预报界面设计

【C QT项目5】——基于HTTP与JSON数据流的天气预报界面设计 一、项目概述二、UI设计与stylesheet样式表三、天气预报数据接口四、JSON数据4.1 概述4.2 QT生成JSON数据4.3 QT解析JSON数据4.4 将JSON数据解析到QMap中 五、软件开发网络通信架构5.1 BS架构/CS架构5.2 HTTP基本概念…

探索Java11新世界:JDK 11新特性详解

博主猫头虎的技术世界 🌟 欢迎来到猫头虎的博客 — 探索技术的无限可能! 专栏链接: 🔗 精选专栏: 《面试题大全》 — 面试准备的宝典!《IDEA开发秘籍》 — 提升你的IDEA技能!《100天精通鸿蒙》 …

Python自定义logger模块(附Demo)

目录 1. 内置logger2. 自定义logger 1. 内置logger Python标准库中的logging模块提供了日志记录的功能 允许开发者通过创建日志记录器、处理程序和格式化器来控制日志的生成和输出 以下是logging模块的一些主要组件和概念: 日志记录器 (Logger):整个…

JavaScript中的内存泄漏

一、是什么 内存泄漏(Memory leak)是在计算机科学中,由于疏忽或错误造成程序未能释放已经不再使用的内存 并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失…

【前端素材】推荐优质医院后台管理系统I-Health平台模板(附源码)

一、需求分析 后台管理系统是一种用于管理和监控网站、应用程序或系统的在线工具。它通常是通过网页界面进行访问和操作,用于管理网站内容、用户权限、数据分析等。后台管理系统是网站或应用程序的控制中心,管理员可以通过后台系统进行各种管理和配置操…

基于自适应波束成形算法的matlab性能仿真,对比SG和RLS两种方法

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 5.完整程序 1.程序功能描述 基于自适应波束成形算法的matlab性能仿真,对比SG和RLS两种方法. 2.测试软件版本以及运行结果展示 MATLAB2022a版本运行 3.核心程序 ........................…

深度学习基础(一)神经网络基本原理

之前的章节我们初步介绍了机器学习相关基础知识,目录如下: 机器学习基础(一)理解机器学习的本质-CSDN博客 机器学习基础(二)监督与非监督学习-CSDN博客 机器学习基础(四)非监督学…

智慧校园的未来已来!AI与数字孪生领航教育新时代

随着科技的飞速发展,人工智能(AI)和数字孪生技术正逐渐渗透到我们生活的方方面面,而在教育领域,它们的结合更是催生出一种全新的智慧校园模式。这种模式的出现,不仅预示着教育管理方式的彻底变革&#xff0…

Linux之用户跟用户组

目录 一、简介 1.1、用户 1.2用户组 1.3UID和GID 1.4用户账户分类 二、用户 2.1、创建用户:useradd 2.2、删除用户:userdel 2.3 、修改用户 usermod 2.4、用户口令的管理:passwd 2.5、切换用户 三、用户组 3.1、增加一个用户组:groupadd 3.…

linux 文本编辑命令【重点】

目录 vi&vim介绍 vim安装 vim使用 查找命令 find grep 文本编辑的命令,主要包含两个: vi 和 vim vi&vim介绍 作用: vi命令是Linux系统提供的一个文本编辑工具,可以对文件内容进行编辑,类似于Windows中的记事本 语法: vi file…

微信小程序开发(实战案例):本地生活 - 列表页面开发(动态渲染处理)、节流防抖(节流阀应用)

文章目录 本地生活 - 列表页面开发一、将九宫格分类换成navigator组件二、动态设置商品列表页的 title三、动态渲染商品列表页面四、上拉触底加载数据五、添加Loading加载效果六、数据加载节流防抖处理 本地生活 - 列表页面开发 导入我们上次写的 本地生活 - 首页开发的项目 运…

CCF-CSP: 因子化简(100分)

第一次提交的时候90分,显示的超时,第一反应是难道有死循环? 检查一遍发现并没有,那就是真的超时了,然后翻阅blog,发现不需要去做判断是否是素数这一步,原因是任意一个非素数都是素数乘积构成,比如说&#…

板块二 JSP和JSTL:第四节 EL表达式 来自【汤米尼克的JAVAEE全套教程专栏】

板块二 JSP和JSTL:第四节 EL表达式 一、什么是表达式语言二、表达式取值(1)访问JSP四大作用域(2)访问List和Map(3)访问JavaBean 三、 EL的各种运算符(1).和[ ]运算符&…

2.21学习总结

1.【模板】ST 表 2.Balanced Lineup G 3.景区导游 4.最近公共祖先(LCA) 倍增思想:主要用于LCA问题,RMQ问题。在进行 递推 时,如果 状态空间很大,通常的 线性递推 无法满足 时间 与 空间复杂度 的要求&…

【GPTs分享】GPTs分享之Write For Me

Write For Me 是一个专门定制的GPT版本,旨在为用户提供高质量的文本内容创作服务。它适用于各种写作需求,从商业计划、学术文章到创意故事等。下面是从简介、主要功能、使用案例、优点和局限性几个方面对Write For Me 的详细介绍。 简介 Write For Me …

java基于redis实现分布式锁

文章目录 前言一、redis二、Redisson1.引入库2. 分布式锁3. 锁自动续期 总结 前言 上篇文章介绍了Java中锁的应用,在SpringBoot单体应用中完全够用,但是SpringCloud微服务集群中就力所不及了。 我的使用场景是某些微服务应用中使用spring注解的形式来完成定时任务的功能,服务集…

深度学习系列59:文字识别

1. 简单文本: 使用google加的tesseract,效果不错。 首先安装tesseract,在mac直接brew install即可。 python调用代码: import pytesseract from PIL import Image img Image.open(1.png) pytesseract.image_to_string(img, lan…

规则持久化(Sentinel)

规则持久化 基于Nacos配置中心实现推送 引入依赖 <dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-datasource-nacos</artifactId> </dependency> 流控配置文件 [{"resource":"/order/flow",…