G1垃圾回收器

​ G1垃圾回收器是可以同时回收新生代和老年代的对象的,不需要两个垃圾回收器配合起来运作,他一个人就可以搞定所有的垃圾回收,最大的一个特点,就是把Java堆内存拆分为多个大小相等的Region,并且可以让我们设置一个垃圾回收的预期停顿时间

​ 我们直接可以给G1指定,在一个时间内,垃圾回收导致的系统停顿时间不能超过多久,G1全权给你负责,相当于我们就可以直接控制垃圾回收对系统性能的影响,G1可以做到让你来设定垃圾回收对系统的影响,他自己通过把内存拆分为大量小Region,以及追踪每个Region中可以回收的对象大小和预估时间,最后在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时 间内尽量回收尽可能多的垃圾对象。

​ 触发垃圾回收的时候,可以根据设定的预期系统停顿时间,来选择最少回收时间和最多回收对象的Region进行垃圾回收,保证GC 对系统停顿的影响在可控范围内,同时还能尽可能回收最多的对象。

​ G1提供了专门的Region来存放大对象,而不是让大对象进入老年代的Region中,在G1中,大对象的判定规则就是一个大对象超过了一个Region大小的50%,比如按照上面算的,每个Region是2MB,只要一个大对 象超过了1MB,就会被放入大对象专门的Region中,而且一个大对象如果太大,可能会横跨多个Region来存放,其实新生代、老年代在回收的时候,会顺带带着大对象Region一起回收,所以这就是在G1内存模型下对大对象的分配和回 收的策略。

G1老年代垃圾回收机制

  • ​ 首先会触发一个“初始标记”的操作,这个过程是需要进入“Stop the World”的,仅仅只是标记一下GC Roots直接能引用的对象, 这个过程速度是很快的

  • 接着会进入“并发标记”的阶段,这个阶段会允许系统程序的运行,同时进行GC Roots追踪,从GC Roots开始追踪所有的存活对象

  • 接着是下一个阶段,最终标记阶段,这个阶段会进入“Stop the World”,系统程序是禁止运行的,但是会根据并发标记 阶段记录的 那些对象修改,最终标记一下有哪些存活对象,有哪些是垃圾对象

  • 最后一个阶段,就是“混合回收“阶段,这个阶段会计算老年代中每个Region中的存活对象数量,存活对象的占比,还有执行垃圾回 收的预期性能和效率

  • 接着会停止系统程序,然后全力以赴尽快进行垃圾回收,此时会选择部分Region进行回收,因为必须让垃圾回收的停顿时间控制在我 们指定的范围内。

    image-20201124201850642

一般在老年代的Region占据了堆内存的Region的45%之后,会触发一个混合回收的过程,也就是Mixed GC,分为了好几个阶段。比如先停止工作,执行一次混合回收回收掉 一些Region,接着恢复系统运行,然后再次停止系统运行,再执行一次混合回收回收掉一些Region。-XX:G1MixedGCCountTarget就是在一次混合回收的过程中,最后一个阶段执行几次混合回收,默认值是8次

那么为什么混合回收要反复回收多次呢?

​ 因为你停止系统一会儿,回收掉一些Region,再让系统运行一会儿,然后再次停止系统一会儿,再次回收掉一些Region,这样可以尽 可能让系统不要停顿时间过长,可以在多次回收的间隙,也运行一下。

​ 还有一个参数,就是“-XX:G1HeapWastePercent”,默认值是5%,意思就是说,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他 Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会 立即停止混合回收,意 味着本次混合回收就结束了。

​ 还有一个参数,“-XX:G1MixedGCLiveThresholdPercent”,他的默认值是85%,意思就是确定要回收的Region的时候,必须是存活对象低于85%的Region才可以进行回收,否则要是一个Region的存活对象多余85%,你还回收他干什么?这个时候要把85%的对象都拷贝到别的Region,这个成本是很高的。

回收失败时Full GC

​ 如果在进行Mixed回收的时候,无论是年轻代还是老年代都基于复制算法进行回收,都要把各个Region的存活对象拷贝到别的Region 里去,此时万一出现拷贝的过程中发现没有空闲Region可以承载自己的存活对象了,就会触发 一次失败。一旦失败,立马就会切换为停止系统程序,然后采用单线程进行标记、清理和压缩整理,空闲出来一批Region,这个过程是极慢的。

G1什么时候触发新生代垃圾回收?

Eden区域的空间不够了,就触发新生代gc,但是到底什么时候Eden区域会内存不够呢?

​ 会根据你预设的gc停顿时间,给新生代分配一些 Region,然后到一定程度就触发gc,并且把gc时间控制在预设范围内,尽量避免一次性回收过多的Region导致gc停 顿时间超出预期。

新生代gc如何优化?

  • 对于G1而言,我们首先应该给整个JVM的堆区域足够的内存
  • 该合理设置“-XX:MaxGCPauseMills”参数,如果这个参数设置的小了,那么说明每次gc停顿时间可能特别短,此时G1一旦发现你对几十个Region占满了就立即触 发新生代gc,然后gc频率特别频繁,虽然每次gc时间很短

mixed gc如何优化?

​ mixed gc的触发,是老年代在堆内存里占比超过45%就会触,其实核心的点,还是**-XX:MaxGCPauseMills**这个参数。假设你“-XX:MaxGCPauseMills”参数设置的值很大,导致系统运行很久,新生代可能都占用了堆 内存的60%了,此时才触发新生代gc,那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。或者是你新生代gc过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor 区域的50%,也会快速导致一些对象进入老年代中。

​ 所以这里核心还是在于调节-XX:MaxGCPauseMills这个参数的值,在保证他的新生代gc别太频繁的同时,还得考虑每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc。