G1垃圾收集器
HotSpot JDK 7从update 4开始引入了G1垃圾收集器。
G1收集器是服务器风格的垃圾回收器,主要针对多处理器机器上占用大量内存的应用。G1能缩短暂停时间,也能提供高吞吐量。
与CMS的区别
和CMS(Concurrent Mark-Sweep)收集器相比,G1具备压缩功能,能避免碎片问题;G1的暂停时间更加可控,用户可以指定暂停时间指标。
以前的垃圾收集器(顺序、并行、CMS)都把堆分为三个部分:年轻代、老年代、永久代,三个部分的大小都是固定的。而在G1里,堆被分为若干区域,每个区域里的内存是连续的。区域会有“角色”,但某个“角色”的大小并不固定,这就提供了更大的内存使用灵活度。
执行垃圾回收的时候,G1的操作和CMS有些类似,G1也会和应用并行地进行全局标记。标记阶段结束后,G1会优先收集存活对象少(占用空间小)的区域,也就是垃圾(可回收对象)多的区域。G1采用“pause prediction模型”,根据用户定义的暂停时间指标确定回收区域的个数。
G1要回收的区域会被清空,里面的存活对象会被拷贝到堆里的另一个单独区域,压缩、释放原区域里的内存。在多处理器机器上,这会并行地执行,减少暂停时间、增加吞吐量。
但G1不是实时收集,它会基于前面收集的相关数据进行评估,看在用户指定的暂停时间里能收集几个区域。
G1里有两个数据结构:
- Remembered Sets(RSets):每个堆区域都有一个RSet,记录该区域里的对象引用。有了RSet,就可以对区域进行并行、独立的收集。
- Collection Sets(CSets):GC要收集的区域集合,集合里的区域可以是任意角色(Eden、Survivor或老年代)。CSet里记录的区域都会被清空(回收掉或者移走)。
适用场景
G1适合堆大小差不多是6GB或者更大,暂停时间要求在0.5秒以下的场景。如果应用具备如下一个或多个特征用G1会有比较好的效果:
- Full-GC执行太频繁,或者持续的时间太长
- 对象分配的速度差距较大
- 不希望GC暂停时间超过0.5-1秒
但如果应用或系统的GC暂停时间本来就不长,建议还是保持原先的GC,不要换成G1
工作步骤
堆被划分为很多区域,区域大小由JVM确定(用户也可以设置),通常是1MB-32MB,大约2000个。
区域会具备“Eden”、“Survivor”、“老年代”的角色,但它们不是连续的。还有另外一种区域,叫Humongous区域,它用来存放大对象(大小超过区域大小的50%),这些区域是连续的。
年轻代
存活对象会被拷贝/移动到一个或多个“Survivor”区域,存活时间够长的直接移到“老年代”区域。这里会stop the world,但young GC的过程是多线程执行的。
老年代
1、初始标记(stop the world):标记引用老年代里对象的Survivor区域,这些Survivor区域叫root区域 GC pause (young)(inital-mark)
2、扫描root区域:扫描survivor区域,找到引用老年代里内容的对象。young GC开始前要完成。
3、并发标记:找出整个堆里可达的对象,计算各个区域的对象存活率。这个阶段可能会被年轻代的GC中断。
4、Remark(stop the world):对并发标记阶段的结果查漏补缺,使用snapshot-at-the-beginning(SATB)算法,比CMS使用的算法快很多;而且会回收空区域(没有存活对象的区域)
5、清理:先stop the world,收集对象存活率最低的区域,然后清理Remembered Set。接着应用可以运行了,这时会重置空闲区域,并将这些区域返回给free list。年轻代和老年代同时回收
6、拷贝(stop the world):将存活对象拷贝到没有使用的区域里
命令行选项
G1可用的命令行选项有:
-XX:+UseG1GC——让JVM使用G1垃圾回收器
-XX:MaxGCPauseMillis=200——设置GC暂停时间目标值,缺省200毫秒。但这不是硬指标,JVM会尽力满足。
-XX:InitiatingHeapOccupancyPercent=45——整个堆被占用多少之后开始进行GC,缺省为45,0表示持续不停进行GC
-XX:NewRatio=n——年轻代和老年代的比例,缺省为2
-XX:SurvivorRatio=n——Eden和Survivro的比例,缺省为8
-XX:G1ReservePercent=n——保留的堆大小,减少晋升过程中出错的可能性,也就是增加可用的to-space内存,缺省是10
-XX:G1HeapRegionSize=n——G1中,堆分为大小相等的区域。这个参数设置区域的大小,缺省值取决于堆的总大小,有效取值是1M-32M。
最佳实践
使用G1时的最佳实践
1、不要设置年轻代的大小(-Xmn),否则会扰乱G1的缺省行为,JVM也不会满足用户指定的暂停时间。而且设置了固定值的话,G1将无法随需扩展年轻代的大小
2、GC暂停时间不是100%能保证的
3、如果GC的晋升过程中遇到堆区域溢出(使用-XX:+PrintGCDetails看到to-space overflow),可以通过下面几种方式避免:
- 增加-XX:G1ReservePercent=n,缺省值是10。这可以增加可用的to-space内存
- 使用-XX:ConcGCThreads=n增加标记线程数目