BOEHM GC原理及总结

BOEHM GC原理

从上次分配原理中知道,给出一个指针,可以根据二级数组找到HBLKHDR的描述信息,根据HBLKHDR又能知道其对应的OBJ大小。再根据指针对齐原理,知道指针会存储在OBJ内的地方(遍历整个OBJ,将可能为指针的都拿出来检查)。经过这几层可以得到引用树。

具体实现为首先将所有线程暂停(pthread_kill SUSPEND,当然不能把自己这个线程也挂起,相关宏为#define STOP_WORLD GC_stop_world)

然后访问各个线程的堆栈顶及底部(GC创建每个线程时会获取底部指针,而线程在处理SUSPEND信号时,会将当前栈顶指针保存到GC_THREAD结构中,每个线程都对应一个GC_THREAD结构),然后遍历所有线程的栈内存,按照指针存储对齐原理。

获取可能的指针(判定该指针是否是有效指针的方式是:BOEHM会保存自己从HEAP分配内存的最低地址和最高地址,若判定的指针位于该区间,则认为其可能为一个指针),再通过二级数组获取该指针对应的HBLKHDR。

若未找到(这种属于异常情况了,未找到,却可能被引用,有可能是非BOEHM分配的内存,则将该内存对应PAGE地址放入BLACKLIST中,不能用来分配了,否则有几率存在碰撞可能,一般情况下很少会发生,这个应该是BOEHM后来打的补丁,暂不关注)

找到HBLKHDR后,会检查该PAGE是否为空闲状态(已被分配到ok_freelist中后,PAGE会被设置为非空闲)

若为空闲,则忽略该指针(因为未指向有效对象),若是,则标记该指针指向的OBJ为使用状态,并将对应的标记位设置为可用(每给出一个指针,都能根据HBLKHDR知道该OBJ的大小,因为其中存储了hb_sz字段),然后再遍历该OBJ,找出可能的指针,并一一标记。
在整个标记过程开始前,GC会清除所有HBLKHDR的标记字段(此时其他线程已暂停),所有的HBLKHDR有一个队列头,遍历队列能获取到所有的HBLKHDR。然后标记完成后,被标记的说明存在引用,未被标记的则会被释放到ok_freelist,若整个HBLKHDR所有标记都未被设置,则会将HBLKHDR还回到GC_hblkfreelist中,并且会根据其地址,将相邻的PAGE合并成大PAGE再调整其在GC_hblkfreelist中的存储位置。

这里介绍时忽略了其执行的细节(因为研究时重点看了其管理内存的方式,对于回收分为几个过程,并通过管理回收状态来标记和清扫,并最终回收,整个过程的代码较为晦涩,所以只管理其重点部分)。

相关堆栈

这里未研究其他全局数据GC方式,原理跟堆栈中引用类似。

总结

MONO按照16字节内存的对齐方式分配内存,每分配一个对象时,会根据其内存大小并尝试分配ok_freelist,若程序存在一堆小内存对象,且其对象大小为16,32,48等内存大小,则会生成三个ok_freelist链表,每个链表占用了一个PAGE,若这些对象数量不大,则会导致内存的浪费。

将16和32字节大小的对象定义更大,让其接近48字节,这样所有的小对象都未48字节,反而能节省MONO内存(因为只用分配一个PAGE即可以满足这些小内存对象的分配)。 定义的对象占用内存尽量压缩,因为GC时会遍历整个OBJ,因此如果OBJ越大,那么GC访问的内存就会越多,当然也就越慢,因此对象在能实现机制的前提下,越小越好。


上述两条看起来有些冲突,其表达的意思是:

对于一些小对象,尽量让其按照某一个GRANULE对齐(当然,若某种GRANULE的对象有很多,则不需要特地处理),尽量节省内存(因为如果某个GRANULE的对象很少,则浪费了1整个PAGE);而GC会遍历内存,因此定义对象时,内存能用更小的字节表达,则尽量小,一些字段顺序进行调整能使得编译时进行内存对齐的开销更小,有可能也能节省部分内存。

相关资源及从系统分配内存对齐方式

Mono官网 http://www.mono-project.com/docs/
BOEHM项目官网 http://www.hboehm.info/


其使用到的与从堆中分配内存相关的函数为sbrk,同时也调用了一个不常用的函数mprotect,用来改变内存段属性(会用来作为辅助调试的方式),在预分配时先改为PROT_NONE,只有真正分配给应用使用时,才设置为可访问,这样如果存在内存越界等行为,可以在测试阶段发现。GC_unix_sbrk_get_mem接口会调用sbrk,分配内存前会保证分配给上层的地址是按照页面对齐的,

其具体实现为:

因为其上面所描述的分配HBLKHDR描述PAGE块时,基本前提是内存块按照PAGE_SIZE对齐。
 

CUBE调用的部分接口回顾

1.通过阅读MONO GC算法,回头再看CUBE调用的接口mono_object_is_alive,就比较容易理解了,是通过读取二级数组获取到对应指针的HBLKHDR后,查找其中对应的hb_mark BIT位,来判定某个OBJ是否存活;

2.Cube通过调用mono_object_is_alive来判定对象存活,然后该接口在sgen GC算法中已废弃,若UNITY后续更新MONO版本,CUBE该功能将可能失效(需要用新的方式);


3.Mono_object_is_alive一定要在gc_collect()调用完成后才能调用,否则无法获取到正确信息(因为只有GC完成后才能判定是否存活,在GC_malloc调用后,并不会设置HBLKHDR相应的mark标记(这么做应该为了简便,因为只有GC回收内存时才需要标记,分配内存不标记,待GC阶段再标记));

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Boehm GC是一个用于C和C++语言的垃圾回收库。它可以帮助程序员自动回收不再需要的内存,从而减轻手动内存管理的负担。下面是使用Boehm GC的基本步骤: 1. 下载Boehm GC库并安装 你可以从官方网站(https://www.hboehm.info/gc/)下载Boehm GC库,并按照官方文档进行安装。安装完成后,你需要将库的头文件和库文件添加到编译器的搜索路径中。 2. 将代码链接到Boehm GC库 在编译你的代码时,需要将Boehm GC库链接到你的程序中。具体的链接方式取决于你使用的编译器,但通常需要在编译命令中加入-lgc参数。 3. 在代码中使用Boehm GC库 在你的代码中,需要使用Boehm GC库提供的API来分配内存、注册对象等。例如,你可以使用GC_MALLOC函数来分配内存,使用GC_REGISTER_FINALIZER函数来注册对象的终结器等。 以下是一个简单的示例,演示如何使用Boehm GC库: ``` #include <stdio.h> #include <gc.h> int main() { int *p = (int*) GC_MALLOC(sizeof(int)); *p = 42; printf("%d\n", *p); return 0; } ``` 在这个示例中,我们使用了GC_MALLOC函数来分配一个整型变量所需的内存,并将其初始化为42。由于我们使用了Boehm GC库提供的内存分配函数,所以我们不需要手动释放内存。当程序运行结束时,Boehm GC库会自动回收不再需要的内存。 需要注意的是,Boehm GC库并不是所有情况下都是最佳的选择。在某些情况下,手动管理内存可能是更好的选择,例如需要精确控制内存分配和释放时。因此,使用Boehm GC库时需要根据自己的具体情况进行权衡。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值