G1回收器
官方给G1设定的目标实在延迟可控的情况下获得尽可能高的吞吐量,所以才担当起”全功能收集器“的重任与期望。
G1时一个并行回收器,它把堆内存分割为很多不相关的区域,使用不同的Region来表示Eden,幸存者0区,幸存者1区,老年代等。
G1 GC有计划的避免在整个Java堆中进行全区域的垃圾收集。G1跟踪这个Region里面的垃圾堆积的价值大小,在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region。
由于这种方式的侧重点在于回收垃圾最大量的区间,所以我们给G1一个名字,垃圾优先
G1回收器的特点
与其他GC收集器相比G1使用了全新的分区算法,其特点如下所示:
- 并行与并发
- 并行性:在G1回收期间,可以有多个GC线程同时工作,有效利用多核计算能力,此时用户线程STW
- 并发性:G1拥有与应用程序交替执行的能力,部分工作可以和应用程序同时执行,因此,一般来说,不会再整个回收阶段发生完全阻塞应用程序的情况
- 分代收集
- 从分代上看,G1依然属于分代型垃圾回收器,他会区分年轻代和老年代,年轻代依然有Eden区和Survivor区。但从堆结构上看,它不要求整个Eden区,年轻代或者老年代时连续的,也不再坚持固定大小和固定数量。
- 将堆空间分为若干个区域,这些区域中包含了逻辑上的年轻代和老年代。
- 和之前的各类回收器不同,它同时监护年轻代和老年代,对比其他回收器,或者工作在年轻代,或者工作在老年代。
空间整合
- CMS:”标记-清除“算法、内存碎片、若干次GC后进行一次碎片整理
- G1将内存划分为一个个的region。内存的回收是以region作为基本单位的。Region之间是复制算法,但整体上实际可看作是标记-压缩算法,两种算法都可以避免内存碎片。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前出发下一次GC。尤其是当Java堆非常大的时候,G1的优势更加明显。
可预测的停顿时间模型(Soft real-time)
这是G1相对于CMS的另一大优势,G1除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N毫秒
- 由于分区的原因,G1可以只选取部分区域进行内存回收,这样缩小了回收的范围,因此对于全局停顿的情况的发生也能得到较好的控制。
- G1跟踪各个Region里面的垃圾堆积的价值大小,在后台维护一个优先泪飙,每次根据允许的收集时间,优先回收加指最大的Region,保证了G1收集器在优先的时间内可以获取尽可能高的收集效率。
- 相比与CMS GC,G1未必能做到CMS在最好情况下的延时停顿,但最差的情况要好很多。
G1回收器的缺点
相较于CMS,G1还不具备全方位,压倒性的优势。比如再用户程序运行过程中,G1无论时为了垃圾收集产生的内存占用还是程序运行时的额外执行负载都要比CMS高,另外,再小内存应用上CMS的表现大概率会优于G1,而G1再大内存应用上则发挥其优势,平衡点在6-8G。
G1回收器的参数设置
G1回收器的常见操作步骤
G1的设计原则就是简化JVM性能调优,开发人员只需要简单的三步即可完成
- 开启G1垃圾收集器
- 设置堆的最大内存
- 设置最大的停顿时间
G1中提供了三种垃圾回收模式,YoungGC、MixedGC和Full GC,在不同的条件下触发。
G1回收器的使用场景
分区Region:化整为零
使用G1收集器时,它将整个Java堆划分成约2048个大小相同的独立Region块,每个Region块大小根据堆空间的实际大小而定,整体被控制在1MB到32MB之间,且为2的N次幂,即1MB,2MB,4MB,8MB,16MB,32MB。可以通过XX:G1HeapRegionSize设定。所有的Region大小相同,且在JVM生命周期内不会被改变。
虽然还保留有新生代和老年代的概念,但时新生代和老年代不再时物理隔离的了,他们都是一部分Region的集合,通过Region的动态分配方式实现逻辑上的连续。
- 一个region有可能属于Eden,Survivor或者Old/Tenured内存区域。但是一个region只可能属于一个角色。图中的E边是该region属于Eden内存区域,S表示属于Survivor内存区域,O表示属于Old内存区域。图中空白的表示未使用的内存空间
- G1垃圾收集器还增加了一种新的内存区域,叫做Humongous内存区域,如图中的H块。主要用于存储大对象,如果超过1.5个region,就放到H。
设置H的原因:
对于堆中的大对象,默认会被分配早老年代,但是如果它时一个短期存在的大对象,就会堆垃圾收集器造成负面影响,为了解决这个问题,G1划分了一个Humongous区,它用来专门存放大对象,如果一个H区装不下一个大对象,那么G1会寻找连续的H区来存储。为了能找到连续的H区,有时候不得不启动Full GC。G1的大多数行为都把H区作为老年代的一部分来看待。
Remembered Set
- 一个对象被不同区域引用的问题
- 一个Region不可能时孤立的,一个Region中的对象可能被其他任意Region中的对象引用,判断对象存活时,是否需要扫描整个Java堆才能保证准确。
解决方法
- 无论G1还是其他分代收集器,JVM都是使用Remembered Set来避免全局扫描。
- 每个Region都有一个对应的Remembered Set:
- 每次Reference类型数据写操作时,都会产生一个Write Barrier暂时中断操作:
- 检查将要写入的引用指向的对象是否和该Reference类型数据在不同的Region
- 如果不同,通过CardTable把相关引用信息记录到引用指向对象的所在Region对应的Remembered Set中;
- 当进行垃圾收集时,在GC根节点的枚举范围加入Remembered Set,就可以保证不进行全局扫描,也不会有遗漏。
G1回收器垃圾回收的过程
G1 GC的垃圾回收过程主要包括如下三个换节:
- 年轻代GC(Young GC)
- 老年代并发标记过程(Concurrent Marking)
- 混合回收(Mixed GC)
- 如果需要,单线程、独占式、高强度的Full GC还是继续存在的,它针对GC的评估失败提供了一种失败保护机制,即强力回收。
G1回收过程一:年轻代GC
然后开始下面的回收过程
- 扫描根
根是指static变量指向的对象,正在执行的方法调用链条上的局部变量等。根引用连同RSet记录的外部引用作为扫描存活对象的入口
- 更新RSet
处理dirty card queue中的card。更新RSet。此阶段完成后,RSet可以准确的反应老年代对所在的内存分段中对象的引用。
- 处理RSet
识别被老年代对象指向的Eden中的对象,这些被指向的Eden中的对象被认为是存活的对象。
- 复制对象
此阶段,对象被遍历,Eden区内存段中存活的对象会被复制到Survivor区中空的内存分段,Survivor区内存段中存活的对象如果年龄未达到阈值,年龄会加1,达到阈值会被复制到Old区中的空的内存分段。如果Survivor空间不够,Eden空间的部分数据会直接晋升到老年代空间
- 处理引用
处理Soft,Weak,Phantom,Final,JNI Weak等引用。最终Eden空间的数据为空,GC停止工作,而目标内存中的对象都是连续存储的,没有碎片,所以复制过程可以达到内存整理的效果,减少碎片。
G1回收过程二:并发标记过程
- 初始标记阶段:标记从根节点直接可达的对象,这个阶段时STW的,并且会触发一次年轻代GC。
- 根区域扫描:G1 GC扫描Survivor区直接可达的老年代区域对象,并标记被引用的对象。这一过程必须在young GC之前完成。
- 并发标记:在整个堆中进行并发标记(和应用程序并发执行),此过程可能被young GC中断。在并发标记阶段,若发现区域对象中所有对象都是垃圾,那么这个区域会被立即回收。同时,并发标记过程中,会计算每个区域的对象活性(区域中存活对象的比例)。
- 再次标记:由于应用程序持续进行,需要修正上一次的标记结果。是STW的。G1中采用了比CMS更快的初始快照算法:snapshot-at-the-beginning(SATB)。
- 独占清理:计算各个区域的存活对象和GC回收比例,并进行排序,识别可以混合回收的区域。为下阶段做铺垫,是STW的。
- 并发清理阶段:识别并清理完全空闲的区域。
G1回收过程三:混合回收(YoungGC和OldGC)
当越来越多的对象晋升到老年代old region时,为了避免堆内存被耗尽,虚拟机会触发一个混合的垃圾收集器即Mixed GC,该算法并不是一个Old GC,除了回收整个Young Region,还会回收一部分Old Region。这里需要注意,是一部分老年代,而不是全部老年代。可以选择哪些Old Region进行收集,从而可以堆垃圾回收的耗时时间进行控制。也要注意的是Mixed GC并不是Full GC。
- 并发标记结束后,老年代中百分百位垃圾的内存分段被回收了,部分为垃圾的内存分段被计算了出来。默认情况下,这些老年代的内存分段会分8次被回收。
- 混合回收的回收集包括八分之一的老年代内存分段,Eden区内存分段,Survivor区内存分段。混合回收的算法和年轻代回收的算法完全一致。
- 由于老年代中的内存分段默认分8次回收,G1会优先回收垃圾多的内存分段。垃圾占内存分段比例高的,越会被先回收。并且有一个阈值会决定内存分段是否被回收,
-XX:G1MixedGCLiveThreasholdPercent
,默认为65%,意思是垃圾占内存分段比例要达到65%才会被回收,如果垃圾占比太低,意味着存活的对象占比高,在复制的时候会花费更多的时间 - 混合回收并不一定要进行8次,有一个阈值
-XX:G1HeapWastePercent
默认值为10%,允许整个堆内存有10%的空间被浪费,意味着如果发现可以回收的垃圾占堆内存的比例低于10%,则不在进行混合回收。因为GC会花费很多时间但是回收到的内存却很少。