G1垃圾收集器,是一款面向服务器的垃圾收集器。主要针对多颗处理器以及大容量内存的机器。可以满足低停顿时间的同时具备高吞吐量。
G1将内存区域划分成多个大小相等独立的region,JVM最多有2048个Region。一般Region大小等于堆的大小 / Region 个数。如果堆为4096M,则每个Region的大小为 2M。可以通过-XX:G1HeapRegionSize来指定每个Region的大小,但是推荐默认的计算方式。
G1垃圾收集器保留了年轻代和老年代的概念,但是不在是物理隔离。他们都是不连续的Region的集合。默认年轻代的大小为堆的5%,如果堆的大小为4096M,那么年轻代的大小约为200M,对应为100个Region,可以通过-XX:G1NewSizePercent设置年轻代的初始占比,在系统运行中,JVM在运行过程中会不断的给年轻代分配空间,最多不超过整个堆的60%,可以通过-XX:G1MaxNewSizePercent来调整。年轻代中的Eden和Survivor对应的Region占比为8:1:1。一个Region可能是年轻代,经过垃圾回收以后又会变成老年代,也就是Region的功能是可能动态变化的。
G1垃圾收集器对于什么对象进入老年的原则与分代垃圾收集的原则相同,唯一不同的是对大对象的处理。G1垃圾收集器有专门处理大对象的区域称为Humongous区,而不是让大对象进入老年代的Region中。G1对大对象的判断规则是,如果一个对象的大小超过了Region大小的50%,就会放入到Humongous区域,而且一个对象太大将会跨多个区域存放。Humongous专门用来存放短期巨型对象,这样对象不用直接进入老年代,可以节约老年代空间,避免因为老年代空间不够频繁发生full gc。full gc除了回收年轻代、老年代之外也会对Humongous进行回收。
G1垃圾收集器收集步骤
1、初始标记:初始标记这个过程将会发生STW,标记出对gc roots直接引用的对象。这个过程的速度很快。
2、并发标记:这个阶段用户线程与垃圾回收线程并发执行,根据初始标记过程中标记出的直接引用对象向下进行搜索标记出所有的引用对象,这个过程耗时较长,由于在标记的过程中用户线程也在执行所以在标记的过程中会产生浮动垃圾。
3、重新标记:重新标记就是为了修正并发标记过程中因为用户线程同时运行而导致标记状态发生变化的那部分对象。重新标记的时间要比初始标记长但是要小于并发标记,这个过程中也会发生STW。
4、筛选回收:筛选回收阶段首先对各个Region回收价值和成本进行排序,根据用户所制定的停顿时间(-XX:MaxGCPauseMillis)来制定回收计划。比如老年代有1000个Region已经放满了,本次停顿时间设置为200毫秒,而收集800Region区域的时间正好需要200毫秒,那么就只会回收这800个Region区域(Collection Set 回收集),尽量把回收时间控制在设定的停顿时间之内,这个阶段是需要STW。不管是年轻代或者是老年代都是采用标记复制的算法进行回收,将一个region中存活的对象复制到另外一个region中去,这种算法不会像CMS垃圾回收器那样产生大量的内存碎片。
G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region,比如一个Region花200ms能够回收100M的垃圾,另外一个Region花100ms能够回收200M的垃圾,在回收时间有限的情况下,G1会优先选择回收后面这个Region区域。这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限时间内尽可能提高收集效率。
G1垃圾收集器的特点:
1、并发与并行:G1重复利用CPU、多核环境下的硬件优势,使用多个CPU来缩短STW的时间,部分其他收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让程序继续运行。
2、分代收集:虽然G1可以不需要其他收集器配合就能够管理整个堆但是G1还是保留了分代收集的概念。
3、空间整合:CMS使标记清除的算法进行垃圾回收,需要进行碎片整理。G1从整体上来讲是使用标记整理进行垃圾回收,对于每个Region来说G1使用标记复制的算法进行垃圾回收。
4、可预测低停顿时间:G1除了降低停顿时间之外还可以让使用者指定一个长度为M毫秒的时间段内完成垃圾回收。(-XX:MaxGCPauseMills指定)。
G1垃圾收集分类
Young GC:young gc并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收会需要多长时间,如果时间远远小于-XX:MaxGCPauseMills的设置值,那么增加年轻的region,继续存放新生代对象,不会马上做young gc,直到Eden区放满,G1计算时间与设置的回收参数-XX:MaxGCPauseMills设定的值相近,那么就会触发full gc。
Mixed GC:不是full gc,当老年代达到-XX:InitiatingHeapOccupancyPercents设定的值则触发,回收所有的Young和部分old(根据期望的GC停顿时间确定old垃圾收集的优先顺序)以及大对象区,正常情况下G1的垃圾收集是先做Mixed GC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region中去。拷贝的过程中发现没有足够的region去承载存活的region对象就会触发一次full gc。
Full GC:停止系统线程,启动单线程进行标记、清理和压缩,好空闲出来一部分的region供下一次mixed gc使用。
G1参数设置:
-XX:+UseG1GC:使用G1垃圾收集器
-XX:ParallelGCThreads:指定GC工作线程数量
-XX:G1HeapRegionSize:指定分区大小(1M-32M),GC默认的Region数量为2048
-XX:MaxGCPauseMills:目标停顿时间,默认为200毫秒
-XX:G1NewSizePercent:新生代内存初始空间(默认为堆的5%)
-XX:G1MaxNewSizePercent:新生代内存最大空间
-XX:InitiatingHeapOccupancyPercent:老年代占用空间达到堆内存的阀值(默认45%),则进行新生代、老年代的混合收集(Mixed GC)
-XX:G1MixedGCLiveThresholdPercent:默认为85%,当region中的存活对象低于85%的时候才会进行回收这个region,如果超过这个值,存活对象越多,回收意义不大。
-XX:G1MixedGCCountTarget:再一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收的过程中可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以不回让系统停顿的时间过长。
-XX:G1HeapWastePercent:默认5%,gc过程中空出来的region是否充足阈值,在混合回收的时候,对Region回收都是基于复制算法进行的,都是把要回收的Region里的存活对象放入其他Region,然后这个Region中的垃圾对象全部清理掉,这样的话在回收过程就会不断空出来新的Region,一旦空闲出来的Region数量达到了堆内存的5%,此时就会立即停止混合回收,意味着本次混合回收就结束了。
安全点与安全区
安全点就是指代码中的一些特定的位置,当线程运行到这些位置的时候它的状态是确定的,那么JVM就可以安全的进行一些操作,比如GC等。所以GC不是想什么时候触发就会立即触发的,是需要所有的线程都到达安全点以后才会触发。这些特定的安全点位置有以下几种:
1、方法返回之前
2、调用某个方法结束后
3、抛出异常的位置
4、循环的末尾
大体的思想是当垃圾收集需要中断线程的时候,不直接操对线程操作,仅仅是设置一个标识,各个线程执行的过程中不断地去轮询这个标识,一旦发现标识为真的时候会就近在自己的安全点挂起。
安全区
安全点是对正在执行的线程设定的,如果一个线程处于sleep或者中断状态,它就不能相应JVM的中断请求,再运行到safe point上面。因此JVM引入了安全区,安全区是指一段代码片段中引用关系不会发生变化,在这个区域的任何一点发生GC都是安全的。