垃圾收集算法和垃圾收集器

我们知道,垃圾算法有很多种

  • 标记 - 复制算法
  • 标记 - 清除算法
  • 标记 - 整理算法

垃圾收集器也有很多,分别为新生代垃圾收集器、老年代垃圾收集器、混合收集器 G1
新生代垃圾收集器

  • Serial
  • ParNew
  • Parallel Scavenge

老年代垃圾收集器

  • Serial Old
  • Parallel Old
  • CMS 收集器

他们之间又是各种搭配使用,比如我们现目前最常用的组合 ParNew + CMS 垃圾收集组合
你可能反复记忆了很多次,这些个算法啊,搭配啊,什么情况 Stop The World 可能不久又忘记了
今天从另外一个角度去看这个问题

HotSpot 句柄池访问

在 JVM 中对象的访问定位一般有 2 种方式,分别为句柄直接指针
对于句柄访问来说,栈帧中持有的对象的地址是句柄池对象的地址,需要再次定位到真实对象 为什么会发生 Stop The World - 图1
对于直接指针来说,我们栈帧中持有的对象地址是对象的真实地址,可以直接使用,非常高效 为什么会发生 Stop The World - 图2
但是如果发生 GC 之后对象移动了的话,
对于句柄池来说,我可能只需要修改句柄池中对象指向的地址即可
对于直接指针老说,可能就需要去遍历修改内存中每一个指向当前对象的地址才行,代价相对较高
所以这也是 HotSpot 选择使用句柄池的原因之一

第一个需要 stop the world 的点

既然对象移动了,那么就需要去修改句柄池中的对象指向的地址,这应该很好理解,比如经过年轻代的复制算法之后,对象真实地址位置改变了,那肯定得去修改句柄池中对象的地址,如果不改的话,那么可能就会通过句柄定位到一个错误的对象?
那么怎么改呢?是不是必须得做同步操作,必须等我把地址全部修改完了你才能继续调用,不然就存在错误的风险,这就引出第一个需要 stop the world 的点
也就是说如果采用了复制算法或者整理算法在这个阶段都必须去 stop the world 不管发生在那个代
复制算法,将存活的对象复制到另外一块内存,比如说 YGC 后将 Eden 存活的对象移动到 S0
整理算法,将存活的对象移动到一边,另外一边就是可以清除的垃圾对象
标记清除算法无需 stop the world 也好了解,因为对象地址没有改变嘛

第二个需要 stop the world 的点

我们常用的垃圾收集器组合应该是 ParNew + CMS 垃圾收集器,或者是 G1,对于 CMS 垃圾收集器来说,它是以一种以最短时间回收为目标的垃圾收集器,怎么做到的呢?它将垃圾收集划分为了 4 个阶段

  1. 初始标记(Stop The World)

这一步是枚举根节点 GC Roots,之所以这里会 Stop The Wolrd 是为了得到一个一致的快照结果,不停顿的话,你前面判断好了它是 GC Roots 结点,后面结果这个结果是错误的

  1. 并发标记(不需要 Stop The World)

在这一步,就是通过 GC Roots 去遍历对象图,找出可达对象,不可达的对象即为可清除的

  1. 重新标记(需要 Stop The World)

在这一步有需要 Stop The World 他主要是将在并发标记中发生了变动的那一部分对象单独拎出来重新标记,这是为了得到一个最终一致的结果所以也需要 Stop The World

  1. 并发清除(不需要 Stop The World)

最后采用并发清除算法,上面讲了,由于不涉及对象地址移动,所以无需 stop the world
可以看到 CMS 垃圾收集器将一个长的 Stop The World 垃圾收集拆分成了几个需要 Stop The World 和不需要 Stop The World 的组合
之所以这样能够达到最短回收时间为目标的原因是因为,初始标记非常快,重新标记绝大部分情况下也非常快
为什么初始标记很快呢
因为目前主流的垃圾收集器都是准确垃圾收集,虚拟机是知道哪些地方存放着对象引用的,比如 HotSpot 使用 OopMap 数据结构来记录对象地址,只需要通过对 OopMap 进行扫描即可,无需遍历整个内存,所以通过它的帮助能够快速的完成 GC Roots 的枚举
为什么重新标记很快呢
因为变动的数据大概率为很小一部分,所以代价很低,速度很快

总结

新生代朝生夕灭,一次 GC 可能绝大部分都会消亡,所以采用复制算法,因为枚举 GC Roots 很快,又可以采用并行的收集方式所以往往 YGC 的停顿时间相对比较短
老年代很多都是熬过了多次 GC 的对象,绝大部分都是有效的不能被回收的对象,所以经过一次 GC 可能会还是大部分对象存活,所以不适合用复制算法,由于标记清除算法无需移动对象,无需做 Stop The World 所以选用 CMS 会很快
同时由于 CMS 采用标记清除算法,所以多次 CMS Old GC 后会存在大量的内存碎片可能导致有足够内存而无法配置对象的情况,所以会结合内存合并整理算法,通过参数配置,在每次 CMS Old GC 的时候附带一次合并整理,或者多次 CMS Old GC 附带一次合并整理

作者:随风21
链接:https://juejin.cn/post/6865977008367616014
来源:稀土掘金
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。