保守式GC

简单来说,就是不能识别指针和非指针的GC

寄存器,调用栈和全局变量空间都是不明确的根

以调用栈为例:

调用栈里有调用帧,调用帧里有函数内的局部变量和参数的值,不过,局部变量既有int  double这样的非指针,也有void * 这样的指针,而对于GC调用帧的值只是一堆位的排列,所以,GC并不能识别指针和非指针,所以,叫做不明确的根

保守式GC:把不能识别指针还是非指针的对象当做指针来保守处理,也就是当成活动对象保留下来

比如C里边的union

  
  
  1. union{
  2. long n;
  3. void *ptr;
  4. }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因错误识别对象而压迫堆的问题得到缓解,堆的使用效率提升

缺点:需要花费时间检查黑名单