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阶段再标记));

发布了3 篇原创文章 · 获赞 3 · 访问量 6万+

猜你喜欢

转载自blog.csdn.net/jinangl/article/details/104751338