JVM之G1垃圾回收器
G1垃圾收集器采用的是区域化、分布式的垃圾回收器。堆被划分成 许多个连续的区域(region)。每个区域大小相等,在1M~32M之间。JVM最多支持2000个区域,可推算G1能支持的最大内存为2000*32M=62.5G。区域(region)的大小在JVM初始化的时候决定,也可以用-XX:G1HeapReginSize设置。
虽然在G1收集器里面将整个内存区域都混合在了一起,但是其本身依然也是在小范围内要进行年轻代与老年代的区分,也就是说依然会采用不同的GC方式来处理不同的区域。
内存占用
如果从 ParallelOldGC 或者 CMS收集器迁移到 G1, 您可能会看到JVM进程占用更多的内存(a larger JVM process size). 这在很大程度上与 “accounting” 数据结构有关, 如 Remembered Sets 和 Collection Sets.
Remembered Sets
简称 RSets, 跟踪指向某个heap区内的对象引用. 堆内存中的每个区都有一个 RSet. RSet 使heap区能并行独立地进行垃圾集合. RSets的总体影响小于5%。
Collection Sets
简称 CSets, 收集集合, 在一次GC中将执行垃圾回收的heap区. GC时在CSet中的所有存活数据(live data)都会被转移(复制/移动). 集合中的heap区可以是 Eden, survivor, 和/或 old generation. CSets所占用的JVM内存小于1%。
适用场景
G1的首要目标是为需要大量内存的系统提供一个保证GC低延迟的解决方案. 也就是说堆内存在6GB及以上,稳定和可预测的暂停时间小于0.5秒。
如果应用程序具有如下的一个或多个特征,那么将垃圾收集器从CMS或ParallelOldGC切换到G1将会大大提升性能。
- Full GC 次数太频繁或者消耗时间太长
- 对象分配的频率或代数提升(promotion)显著变化
- 受够了太长的垃圾回收或内存整理时间(超过0.5~1秒)
注意: 如果正在使用CMS或ParallelOldGC,而应用程序的垃圾收集停顿时间并不长,那么继续使用现在的垃圾收集器是个好主意. 使用最新的JDK时并不要求切换到G1收集器。
年轻代垃圾回收
- 堆一整块内存空间,被分为多个heap区(regions)
- 年轻代内存由一组不连续的heap区组成. 这使得在需要时很容易进行容量调整
- 年轻代的垃圾收集,或者叫 young GCs, 会有 stop the world 事件. 在操作时所有的应用程序线程都会被暂停(stopped)
- 年轻代 GC 通过多线程并行进行
- 存活的对象被拷贝到新的 survivor 区或者老年代
垃圾回收前
垃圾回收后
老年代垃圾回收
和 CMS 收集器相似, G1 收集器也被设计为用来对老年代的对象进行低延迟(low pause)的垃圾收集. 下表描述了G1收集器在老年代进行垃圾回收的各个阶段
老年代垃圾回收的几个关键点
1. 并发标记清理阶段(Concurrent Marking Phase)
- 活跃度信息在程序运行的时候被并行计算出来
- 活跃度(liveness)信息标识出哪些区域在转移暂停期间最适合回收.
- 不像CMS一样有清理阶段(sweeping phase).
2. 再次标记阶段(Remark Phase)
- 使用的 Snapshot-at-the-Beginning (SATB, 开始快照) 算法比起 CMS所用的算法要快得多.
- 完全空的区域直接被回收.
3. 拷贝/清理阶段(Copying/Cleanup Phase)
- 年轻代与老年代同时进行回收.
- 老年代的选择基于其活跃度(liveness).
相关参数
例子:
java -Xmx10m -Xms10m -XX:+UseG1GC -XX:+PrintGCDetails TestDemo
总结
G1处理和传统的垃圾收集策略是不同的,关键的因素是它将所有的内存进行了子区域的划分。
CMS和G1的比较
1. CMS用标记——清除算法,G1用标记——整理算法。CMS容易出现内存碎片,G1不会,所以CMS回收完垃圾后要压缩内存,可以配置。不然可能因为碎片太多,大片内存不够导致Full GC,还有可能导致切换到备用的老年代垃圾回收器Serial Old。
2. CMS只能回收老年代,G1可以回收整个堆,因为G1有分代回收的配置。
3. CMS使用并发回收,所以不会导致用户停顿,但是对CPU非常敏感,所以回收时会占用一部分线程,使应用变慢,总吞吐量降低。G1用了并行收集和并发收集,发挥了多个CPU的优势,可以非常精确地控制停顿时间,而且不牺牲吞吐量。