G1回收器

在使用ParNew + CMS进行垃圾回收时,会导致长时间的STW,而STW的本质就是停止系统的工作线程,让系统无法对外服务,同时让GC线程进行垃圾回收的。如果回收时间较长,那么会给系统性能带来较大的影响

因此G1回收器出现了,它的特点有

  • 将Java堆分为多个大小相等的Region

  • 每个Region都有可能属于新生代或老年代

  • 可以设置垃圾回收的预期停顿时间,比如在一小时内最多停顿一分钟

G1如何做到设置垃圾回收的预停顿时间

G1会追踪每个Region中的回收价值(当前Region中有多少垃圾对象,需要耗费多长时间),通过追踪,G1可以比较出本次回收哪些Region的性价比最高(时间最短,回收内存最多),在有限的时间内尽可能回收掉更多的垃圾对象

如何启用G1回收器

可以通过JVM参数-XX:UseG1GC来指定垃圾回收器为G1

如何设定G1的内存大小

在指定了G1回收器后,它会根据-Xmx/2048来计算每个Region的内存大小,因为JVM中最多支持2048个Region,并且每个Region的内存必须是2的倍数

比如你设定的-Xmx4096M,那么按照G1的规则来,4096 / 2048 = 2MB,那么每个Region的内存大小就为2MB

当然,你也可以手动通过JVM参数-XX:G1HeapRegionSize来指定每个Region的大小

系统初始化状态,新生代的内存占比为5%,大概为100个Region,可以通过JVM参数-XX:G1NewSizePercent来设置这个比例,但是一般都推荐保持这个设置,因为G1会动态的调整新生代、老年代的内存占比,但是默认情况下新生代的最大占比不能超过60%,可以通过JVM参数-XX:G1MaxNewSizePercent来设置这个比例

G1中新生代还保留着Eden、Survivor的划分

虽然G1回收器将内存分为了多个Region,但是新生代依旧保留着原本的区域划分,即Eden、Survivor1、Survivor2,并且默认情况下它们的比例为8:1:1,可以通过JVM参数-XX:SurvivorRatio来设置比例,比如系统初始化时,新生代大约有100个Region,那么其中大约有80个Region是Eden区的,10个Region是S1区的,10个Region是S2区的

G1中新生代的垃圾回收算法

G1中新生代在Eden区满时会触发GC,垃圾回收算法还是使用复制算法,同时进入STW,但是因为G1设置了预期停顿时间,可以让系统在GC时指定最多停顿的时间,而不像ParNew回收器那样,极端情况下能够一直停顿,而对系统性能造成严重影响

可以通过JVM参数-XX:MaxGCPauseMills来设置这个时间

G1中对象什么时候进入老年代

和原来一样,进入老年代的条件有,

  1. 超过指定年龄的对象,默认为15年,通过JVM参数-XX:MaxTenuringThreshold指定这个阈值
  2. 动态年龄判断,当某一年龄以下(包括)的对象占有了新生代内存的50%以上,那么所有大于等于该年龄的对象都会进入老年代
  3. 新生代GC后,存活对象内存大小超过Survivor区内存大小,这些对象会进入老年代

G1中如何处理大对象

在G1中,大对象不会进入老年代,G1会提供专门的Region来存放这些大对象

G1中大对象的判定规则为:该对象超过了一个Region大小的50%,那么这个对象就是大对象

大对象可能会横跨多个Region

G1中大对象的回收

会在新生代、老年代发生GC时顺带回收掉

什么时候触发新生代、老年代混合垃圾回收

JVM参数-XX:InitiatingHeapOccupancyPercent,默认为45,即当老年代占堆内存比例达到45%时,即Region数量大约为1000个时,会尝试触发一个混合回收Mixed GC

G1回收器的回收过程

  • 初始标记
  • 并发标记
  • 最终标记
  • 混合回收

初始标记

和CMS一样,这个步骤会触发STW,此时会标记那些被GC Roots直接引用的对象,比如静态变量,局部变量等

并发标记

这个步骤,工作线程和GC线程会同时工作,此时会追踪所有存活的对象,同时JVM会对这个步骤中对象的一些修改记录起来,比如哪个对象被新建了,哪个对象失去了引用等

最终标记

这个步骤会触发STW,对并发标记阶段记录的被修改的对象进行最终标记,找出存活对象和垃圾对象

混合回收

这个步骤会计算老年代中每个Region中存活的对象数量,存活对象的占比和执行垃圾回收的预期性能和效率,然后停止工作线程,对部分Region进行回收,因为G1需要控制垃圾回收时的停顿时间(用户指定)

混合回收的区域包括新生代、老生代和大对象Region

这个阶段会执行多次,JVM参数-XX:G1MixedGCCountTarget指定了混合回收执行的次数,默认为8次,每执行完一次会启动工作线程对外提供服务,一段时间后继续进行STW,执行混合回收,这样可以尽可能的让系统停顿的时间变短,尽量减少对象系统性能的影响

JVM参数-XX:G1HeapWastePercent,默认为5,当G1中Region空闲比例达到5%时,混合回收就结束了

在混合回收时,对Region的回收都是基于复制算法的,即把Region中的存活对象复制到其他Region中,然后清理掉当前Region中的垃圾对象,那么G1回收器进行垃圾回收时,是不会产生内存碎片的

JVM参数-XX:G1MixedGCLiveThresholdPercent,默认为85,G1回收器在确定回收Region时,必须要保证该Region中的存活对象在85%以下,否则使用复制算法的回收效率是非常低的

当G1回收失败时,如在回收时拷贝对象的时候,回收器发现没有空闲的Region可以放入存活对象了,那么就会触发一次回收失败,失败之后,JVM就会切换到单线程回收器,进行标记-清理,压缩和整理,空闲出一批Region,这个过程是非常慢的,又因为它是需要STW的,所以对系统的性能影响非常大