JAVA垃圾收集算法总结以及CMS、G1算法详解( 二 )


CMS以流水线方式拆分了收集周期 , 将耗时长的操作单元保持与应用线程并发执行 。只将那些必需STW才能执行的操作单元单独拎出来 , 控制这些单元在恰当的时机运行 , 并能保证仅需短暂的时间就可以完成 。这样 , 在整个收集周期内 , 只有 两次短暂的暂停(初始标记和重新标记) ,  达到了近似并发的目的 。
CMS收集器优点:并发收集、低停顿 。
CMS收集器缺点:
  • CMS收集器对CPU资源非常敏感 。
  • CMS收集器无法处理浮动垃圾(Floating Garbage) , 所以如果采用CMS算法 , JVM还是在浮动垃圾很多时 , 自动运行fullgc一次来清除浮动垃圾 。
  • CMS收集器是基于标记-清除算法 , 该算法的缺点都有 。
什么情况下CMS比较适合:
(1)响应时间优先 , 能接受牺牲一定的吞吐量 , 如果需要高响应时间和高吞吐量 , 推荐使用G1 。后续文章再继续介绍G1 。G1也是JDK9默认的垃圾收集器;
(2)硬件配置较高 , 即CPU和内存资源充足 。CMS由于需要与用户线程并发执行 , 所以可能会竞争CPU资源 。同时CMS并发标记阶段 , 用户线程同时执行时会新建对象 , 故内存占用会比较高;
(3)堆大小在3G到8G之间 , 同时存活时间较长的对象比较多
下面再简单讲讲G1算法 , 
G1重新定义了堆空间 , 打破了原有的分代模型 , 将堆划分为一个个区域 。这么做的目的是在进行收集时不必在全堆范围内进行 , 这是它最显著的特点 。区域划分的好处就是带来了停顿时间可预测的收集模型:用户可以指定收集操作在多长时间内完成 。即G1提供了接近实时的收集特性 。
G1收集器将整个Java堆划分为多个大小相等的独立区域(Region) , 虽然还保留有新生代和老年代的概念 , 但新生代和老年代不再是物理隔离的了 , 它们都是一部分Region(不需要连续)的集合 。Region的大小是一致的 , 数值是在1M到32M字节之间的一个2的幂值数 , JVM会尽量划分2048个左右、同等大小的Region 。其实这个数字既可以手动调整 , G1也会根据堆大小自动进行调整 。
G1收集的运作过程大致如下:
  • 初始标记(Initial Marking):仅仅只是标记一下GC Roots能直接关联到的对象 , 并且修改TAMS(Next Top at Mark Start)的值 , 让下一阶段用户程序并发运行时 , 能在正确可用的Region中创建新对象 , 这阶段需要停顿线程 , 但耗时很短 。
  • 并发标记(Concurrent Marking):是从GC Roots开始堆中对象进行可达性分析 , 找出存活的对象 , 这阶段耗时较长 , 但可与用户程序并发执行 。
  • 最终标记(Final Marking):是为了修正并发标记期间因用户程序继续运作而导致标记产生变动的那一部分标记记录 , 虚拟机将这段时间对象变化记录在线程Remembered Set Logs里面 , 最终标记阶段需要把Remembered Set Logs的数据合并到Remembered Set中 , 这阶段需要停顿线程 , 但是可并行执行 。
  • 筛选回收(Live Data Counting and Evacuation):首先对各个Region的回收价值和成本进行排序 , 根据用户所期望的GC停顿时间来制定回收计划 。这个阶段也可以做到与用户程序一起并发执行 , 但是因为只回收一部分Region , 时间是用户可控制的 , 而且停顿用户线程将大幅提高收集效率 。
  • 下图就是G1堆空间分布
  •  

JAVA垃圾收集算法总结以及CMS、G1算法详解

文章插图
 
G1特点: