1.G1的特点和由来和G1的核心思想
当ParNew+CMS的组合造成太高的STW的时候,G1在1.7就诞生了。从分代上来看,G1属于分代垃圾回收器,他会区分年轻代和老年代。但是从堆的结构上看,它并不要求整个eden、年轻代和老年代都连续。它使用了分区算法:把Java堆内存拆分为多个大小相等的Region。
而且G1最大的一个特点,就是可以让我们设置一个垃圾回收的预期停顿时间。G1做到这一点主要是因为必须要要追踪每个Region里的回收价值。所以简单来说,G1可以做到让你来设定垃圾回收对系统的影响,他自己通过把内存拆分为大量小Region,以及追踪每个Region中可以回收的对象大小和预估时间,最后在垃圾回收的时候,尽量把垃圾回收对系统造成的影响控制在你指定的时间范围内,同时在有限的时间内尽量回收尽可能多的垃圾对象。
2.Region可能属于新生代也可能属于老年代
G1中,每一个Region时可能属于新生代,但是也可能属于老年代的,刚开始Region可能谁都不属于,然后接着就分配给了新生代,然后放了很多属于新生代的对象,接着就触发了垃圾回收这个Region。然后下一次同一个Region可能又被分配了老年代了,用来放老年代的长生存周期的对象。所以其实在G1对应的内存模型中,Region随时会属于新生代也会属于老年代,所以没有所谓新生代给多少内存,老年代给多少内存这 一说了。实际上新生代和老年代各自的内存区域是不停的变动的,由G1自动控制。
3.什么时候触发并发标记周期回收
G1有一个参数,是“-XX:InitiatingHeapOccupancyPercent”,他的默认值是45%,意思就是说,如果老年代占据了堆内存的45%的Region的时候,此时就会尝试触发一个新生代+老年代一起回收的混合回收阶段。
4.G1垃圾回收的过程
G1的垃圾回收器主要有以下几个过程:初始标记、根区域扫描、并发标记、重新标记、独占清理、并发清理阶段。
初始标记:标记从根节点直接可达的对象,这个阶段往往会伴随一次新生代的GC,会产生全局停顿STW,扫描出所有局部变量、类静态变量的引用对象,这个过程往往很快。
根区域扫描:在上面一个步骤之后,新生代的对象往往在survivor区域,这个阶段主要是对survivor区域直接可达老年代,并标记这些对象。这个过程和应用程序线程是并发执行的,但是根区域不能和新生代GC同时执行(新生代GC修改survivor区域,而根区域扫描依赖这个区域),所以如果此时正在执行新生代GC,那么这次新生代的GC时间将会增长。
并发标记:这个节点是和应用程序并发执行的,主要是对初始标记的变量进行深度追踪,比如GC ROOTS引用的对象等.
重新标记:STW标记,这次使用STAB算法,比CMS的标记算法快很多.根据并发标记阶段记录的那些对象修改,最终编辑哪些存活对象.
独占清理:STW清理,根据限制的停顿时间来最大量的清理垃圾.注意,这里是指堆空间到达参数XX:InitiatingHeapOccupancyPercent指定的值,这次是老年代和年轻代混合回收.G1会根据设置的参数,从老年代和年轻代中各选一些的Region,保证指定时间内回收最多的垃圾.
并发清理阶段:并发清理Remember Set重置
5.G1回收器的一些参数
-XX:G1MixedGCCountTarget: 就是在一次混合回收的过程中,最后一个阶段执行几次混合回收,默认值是8次,意味着最后一个阶段,先停止系统运行,混合回收一些Region,再恢复系统运行,接着再次禁止系统运行,混合回收一些Region,反复8次。
-XX:G1HeapWastePercent:这个参数表示可以被浪费的空间,如果设置默认值为5表示如果混合回收发现堆内存垃圾只剩5%就停止垃圾回收了.(这里可以看出G1回收器整体是基于复制回收,比起CMS少了很多碎片垃圾)
-XX:G1MixedGCLiveThresholdPercent:它的默认值为85%,意思是当回收一个Region时必须要要保证存活的对象少于85%才开始回收.(因为拷贝成本很高,如果减小这个值,那么回收频率升高,可能会将新生代对象提前放入老年代)
6.回收失败时进行Full GC
如果在进行Mixed回收的时候,无论是年轻代还是老年代都基于复制算法进行回收,都要把各个Region的存活对象拷贝到别的Region里去,此时万一出现拷贝的过程中发现没有空闲Region可以承载自己的存活对象了,就会触发 一次失败。
一旦失败,立马就会切换为停止系统程序,然后采用单线程Serial Old垃圾回收器进行标记、清理和压缩整理,空闲出来一批Region,这个过程是极慢极慢的。
7.G1的优化
由上面可知,G1的垃圾回收主要和两个参数有关: -XX:MaxGCPauseMills最大允许停顿时间(默认200MS):如果这个参数较小,那么G1发现几十个Region就达到了预期的分配时间,那么GC的频率就会特别高,虽然时间很短。如果时间特别长,那么G1就会等到积累了很多对象,在一次性回收多个Region,虽然这样GC的频率低了,但是每次停顿的时间会很长。所以这个值只有结合压力测试、GC日志、内存工具结合分析,尽量不让系统的GC频率太高,同时GC的时间不是太长的合理值。
-XX:InitiatingHeapOccupancyPercent堆内存到达多少时开始进行混合回收(默认45%):这个参数代表堆内存使用达到45时就开始进行混合回收,一旦设置不允许虚拟机更改,如果InitiatingHeapOccupancyPercent设置过大那么并发周期迟迟不启动,这样引起Full GC的概率也会增大,反正一个过小的InitiatingHeapOccupancyPercent并发周期非常频繁,大量的GC线程抢占CPU,导致应用程序性能下降。
总结
优化思路:让短期生命对象尽量在新生代回收,长期存活对象提前进入老年代,根据具体的业务场景,合理分配老年代和新生代Eden和Survivor区的大小,其次则是根据系统业务合理设置G1的最大停顿时间.