保守式GC
简单来说,就是不能识别指针和非指针的GC
寄存器,调用栈和全局变量空间都是不明确的根
以调用栈为例:
调用栈里有调用帧,调用帧里有函数内的局部变量和参数的值,不过,局部变量既有int double这样的非指针,也有void * 这样的指针,而对于GC调用帧的值只是一堆位的排列,所以,GC并不能识别指针和非指针,所以,叫做不明确的根
保守式GC:把不能识别指针还是非指针的对象当做指针来保守处理,也就是当成活动对象保留下来
比如C里边的union
union{
long n;
void *ptr;
}ambiguous_data;
它是联合体,所以,GC没法发识别是指针还是非指针,当对象有这样的数据结构时,就可能会识别错误
优点:处理程序不依赖于GC,代码编写者即使没有意识到GC的存在,程序也会自己回收
缺点:
1)识别指针和非指针需要耗费成本
2)错误识别的指针会被当成活动的对象,包括其子对象,可能会造成垃圾对象压迫堆
3)能够使用的GC算法有限
### 精准式GC
可以精准的识别指针和非指针
打标签:利用指针的值是4的倍数的特性(所以低2位都是0),作为识别指针和非指针的依据
1)将非指针的值向左移动1位
2)将低1位置1
注意,打标签的过程不要让数据溢出,用这种方式打标签的话,处理程序里的数值都会是奇数,在程序里进行计算时必须先取消标签,再计算数值,基本上打标签和取消标签都是在程序里手动执行的
优点:不会留下非活动对象
缺点:要求处理程序的配合,给实现者带来麻烦,且每次取消标签,再重新设置,会影响到程序处理的整体速度
#### 间接引用:
根和对象之间是有一个句柄存在的,它持有指向对象的指针,且局部变量和全局变量这些不明确的跟里没有指向对象的指针,值的指针,只装着指向句柄,也就是mutator操作对象时,要经过有句柄的间接引用来执行处理,也就是如果采用间接引用,即使移到了引用目标的对象,也不用改写关键的值,只需要改写句柄里的指针即可
优点:有可能实现GC复制算法和压缩标记算法
缺点:所有对象都是间接引用,会拉低访问对象内数据的速度,这会关系到整个语言的处理速度
#### 保守式GC复制
就是把不明确的根指向的对象以外的对象都复制的GC算法,需要满足以下前提:
1)根是不明确的根
2)没有不明确的数据结构(表明GC可以明确判断对象里的域是指针还是非指针)
3)对象大小随意
4)CPU是32位(不是说,必须是32位,而是在此以32位CPU为例)
堆结构:
堆被分成一定大小的页,那些未被分配到对象的空页则有一个$current_space以外的编号
分配:
1)正在使用的页刚好有符合mutator申请的对象大小的分块,直接分配
2)正在使用的页没有合适的分块,会被分配到新的页,然后将改页设置object标记
3)申请分配大小超过页空间时,和平时分配一样,在开头设定object,然后在第二个页之后设置continued标志
当正在使用的页 + 准备分配的页 >= 总夜大小/2时,就会开始运行GC
GC执行的过程:先对next_space的值++,然后将保留有从根引用对象的页的编号设定为next_page的值,即2,最后GC把所有从根引用的页next_space++之后,就把to页里的对象的子对象复制到空白页,复制完成之后,GC就结束了,这时程序把current_page的值设定为next_space的值
注意,它不会回收包含有从根指向的对象的页里的垃圾对象,而且也不会回收这个垃圾对象所引用的子对象
#### 黑名单机制
黑名单就是把需要注意的地址空间记录下来
在将对象分配到黑名单内的地址时,所分配的对象有以下限制:
1)小对象
2)没有子对象的对象
这样,即使对象成了垃圾且没有被回收也不会有很大的损失
优点:使保守式GC因错误识别对象而压迫堆的问题得到缓解,堆的使用效率提升
缺点:需要花费时间检查黑名单