G1收集器介绍
Garbage First(简称G1) 收集器是垃圾收集器技术发展历史上的里程碑式的成果, 它开创了收集器面向局部收集的设计思路和基于Region的内存布局形式。早在JDK 7刚刚确立项目目标、 Oracle公司制定的JDK 7 RoadMap里面, G1收集器就被视作JDK 7中HotSpot虚拟机的一项重要进化特征。
G1是一款主要面向服务端应用的垃圾收集器。HotSpot开发团队最初赋予它的期望是(在比较长期的) 未来可以替换掉JDK 5中发布的CMS收集器。现在这个期望目标已经实现过半了, JDK 9发布之日, G1宣告取代Parallel Scavenge加Parallel Old组合, 成为服务端模式下的默认垃圾收集器, 而CMS则沦落至被声明为不推荐使用(Deprecate) 的收集器 。
首先要有一个思想上的改变, 在G1收集器出现之前的所有其他收集器, 包括CMS在内, 垃圾收集的目标范围要么是整个新生代(Minor GC) , 要么就是整个老年代(Major GC) , 再要么就是整个Java堆(Full GC) 。而G1跳出了这个樊笼, 它可以面向堆内存任何部分来组成回收集(Collection Set, 一般简称CSet) 进行回收, 衡量标准不再是它属于哪个分代, 而是哪块内存中存放的垃圾数量最多, 回收收益最大, 这就是G1收集器的Mixed GC模式。
G1开创的基于Region的堆内存布局是它能够实现这个目标的关键。虽然G1也仍是遵循分代收集理论设计的, 但其堆内存的布局与其他收集器有非常明显的差异:G1不再坚持固定大小以及固定数量的分代区域划分, 而是把连续的Java堆划分为多个大小相等的独立区域(Region),每一个Region都可以根据需要, 扮演新生代的Eden空间、 Survivor空间, 或者老年代空间。收集器能够对扮演不同角色的Region采用不同的策略去处理, 这样无论是新创建的对象还是已经存活了一段时间、 熬过多次收集的旧对象都能获取很好的收集效果。
Region中还有一类特殊的Humongous区域, 专门用来存储大对象。G1认为只要大小超过了一个Region容量一半的对象即可判定为大对象。每个Region的大小可以通过参数-XX:G1HeapRegionSize设定, 取值范围为1MB~32MB, 且应为2的N次幂。而对于那些超过了整个Region容量的超级大对象,将会被存放在N个连续的Humongous Region之中, G1的大多数行为都把Humongous Region作为老年代的一部分来进行看待, 如下图所示:
image虽然G1仍然保留新生代和老年代的概念, 但新生代和老年代不再是固定的了, 它们都是一系列区域(不需要连续) 的动态集合。G1收集器之所以能建立可预测的停顿时间模型, 是因为它将Region作为单次回收的最小单元, 即每次收集到的内存空间都是Region大小的整数倍, 这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。
G1收集器能建立可预测的停顿时间模型 这句话什么意思?
比如我们希望G1在垃圾回收的时候可以保证1小时内系统停顿(STW)的时间不超过1分钟。
那这个模型就很厉害了!之前我们通过各种JVM参数设置,内存分配就是为了尽可能的减少Minor GC和Full GC带来的停顿,从而影响系统的请求。 而现在通过G1收集器能直接给定一个指定的时间,交给G1全权负责,达成目标!
那么G1是如何做到对垃圾回收导致的系统停顿可控的?
G1收集器之所以能建立可预测的停顿时间模型, 是因为它将Region作为单次回收的最小单元, 即每次收集到的内存空间都是Region大小的整数倍, 这样可以有计划地避免在整个Java堆中进行全区域的垃圾收集。
那么具体的思路是:G1会对每一个Region里回收价值进行追踪,动态的判断如何回收。
啥叫回收价值呢?也就是G1必须搞清楚每一个Region里面到底有多少的垃圾对象需要回收,以及回收这些对象需要消耗多少时间。
比如下图,1个Region中有10MB,需要回收时间为1S,另一个Region中有20MB垃圾对象,需要消耗200ms,那么G1肯定会优先选择时间更少还能回收更多垃圾的200msRegion。
imageG1收集器去跟踪各个Region里面的垃圾堆积的“价值”大小, 价值即回收所获得的空间大小以及回收所需时间的经验值, 然后在后台维护一个优先级列表, 每次根据用户设定允许的收集停顿时间(使用参数-XX:MaxGCPauseMillis指定, 默认值是200毫秒) , 优先处理回收价值收益最大的那些Region, 这也就是“Garbage First”名字的由来。这种使用Region划分内存空间, 以及具有优先级的区域回收方式, 保证了G1收集器在有限的时间内获取尽可能高的收集效率。 这也是G1的核心设计思路。
相关JVM参数:
-XX:+UserG1GC :在JDK8中可以通过手动指定使用G1收集器进行回收
-XX:G1HeapRegionSize=size 指定每一个Region的大小
-XX:MaxGCPauseMillis=time 指定收集的停顿时间,默认是200ms
G1中的Region是如何分配的?每个Region大小是多少?
默认情况下是自己分配和设置,我们依然可以通过参数:“-Xms”和“-Xmx”来设置堆内存的大小,默认情况下是分配2048个Region,比如我们设置堆内存大小为2G,那么分配到2048个Region中,每一个Region的大小就是1MB。而且Region的大小必须是2的倍数,比如1MB,2MB,4MB之类的。
当然我们也可以通过“-XX:G1HeapRegionSize”来手动指定每一个Region的大小。
这里还有一些默认配置需要大家清楚:
- 新生代默认开始分配占比是5%,可以通过“-XX:G1NewSizePercent“来设置新生代初始占比
- 新生代运行过程中最多可以分配到60%的堆内存,可以通过“-XX:G1MaxNewSizePercent”来设置
- 新生代中默认也是按照8:1:1对Eden和survivor去进行分配
下一篇将继续深入分析G1收集器的垃圾回收流程。
网友评论