5.7 G1收集器
G1
是一款面向服务端应用的垃圾收集器。HotSpot
开发团队赋予它的使命是未来可以替换JDK1.5
中发布的CMS
收集器。与其他收集器相比,G1
具备如下特点:
并行于并发:能充分利用多
CPU
、多核环境下的硬件优势,使用多个CPU
来缩短Stop The World
停顿的时间,部分其他收集器原本需要停顿Java
线程执行的GC
动作,G1
收集器仍然可以通过并发的方式让Java
线程继续执行。分代收集:与其他收集器一样,分代概念仍然在
G1
中依然得以保留。虽然G1
可以不需要其他收集器配合就能独立管理整个GC
堆,但它能够采用不同的方式处理创建的对象和已经存活了一段时间、熬过多次GC
的旧对象以获取更好的收集效果。空间整合:
G1
从整体来看是基于“标记-整理”算法实现的收集器,从局部(两个Region
之间)上来看是基于“复制”算法实现的,但无论如何,这两种算法都意味着G1
运作期间不会产生内存空间碎片,收集后能提供规整的可用内存。这种特性有利于程序长时间运行,分配大对象时不会因为无法找到连续内存空间而提前触发下一次GC
。可预测的停顿:这是
G1
相对于CMS
的另一大优势,降低停顿时间是G1
和CMS
共同的关注点,但G1
除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M
毫秒的时间片段内,消耗在垃圾收集上的时间不得超过N
毫秒,这几乎已经是实时Java(RTSJ)
的垃圾收集器的特征了。
在G1
之前的其他收集器进行收集的范围都是整个新生代或者老年代,而G1
不再是这样。使用G1
收集器时,Java
堆的内存布局就是与其他收集器有很大差别,它将整个Java
堆划分为多个大小相等的独立区域(Region
),虽然还保留有新生代和老年代的概念,但新生代和老年代不再是物理隔离了,它们都是一部分Region
(不再要连续)的集合。
G1
收集器之所以能建立可预测的停顿时间模型,是因为它可以有计划地避免在整个Java
堆中进行全区域的垃圾收集。G1
跟踪各个Region
里面的垃圾堆积的价值大小(会后所获得的空间大小以及回收所需时间的经验值),在后台维护一个优先列表,每次根据允许的收集时间,优先回收价值最大的Region
。这种使用Region
划分内存空间以及有优先级的区域回收方式,保证了G1
收集器在有限的时间内可以获取尽可能高的收集效率。
G1
把内存“化整为零”的思路,理解起来似乎很容易,但其中的实现细节却远远没有那样简单。比如,把Java
堆分为多个Region
后,垃圾收集是否就真的能以Region
为单位进行了?听起来顺理成章,再仔细想想就很容易发现问题所在:Region
不可能是孤立的。一个对象分配在某个Region
中,它并非只能本Region
中的其他对象引用,而是可以与整个Java
堆任意的对象发生引用关系。那在做可达性判定确定对象是否存活的时候,岂不是还得扫描整个Java
堆才能保证准确性?这个问题起始并非在G1
中才有,只是在G1
中更加突出而已。在以前的分代收集中,新生代的规模一般都比老年代要小许多,新生代的收集也比老年代要频繁许多,那回收新生代中的对象时也面临相同的问题,如果回收新生代时也不得不同时扫描老年代的话,那么Minor GC
的效率可能下降不少。
在G1
收集器中,Region
之间的对象引用以及其他收集器中的新生代与老年代之间的对象引用,虚拟机都是使用Remembered Set
来避免全堆扫描的。G1
中每个Region
都有一个与之对应的Remembered Set
,虚拟机发现程序在对Reference
类型的数据进行写操作时,会产生一个Write Barrier
暂时中断写操作,检查Reference
引用的对象是否处于不同的Region
之中(在分代的例子中就是检查是否老年代中的对象引用了新生代的对象),如果是,便通过CardTable
把相关引用信息记录到被引用对象所属的Region
的Remembered Set
之中。当进行内存回收时,在GC
根节点的枚举范围中加入Remembered Set
即可保证不对全堆扫描也不会有遗漏。
如果不计算维护Remembered Set
的操作,G1
收集器的运作大致可划分为以下几个步骤:
- 初始标记(
Initial Marking
) - 并发标记(
Concurrent Marking
) - 最终标记(
Final Marking
) - 筛选回收(
Live Data Counting and Evacuation
)
此收集器的前几个步骤和CMS
很相似,初始标记阶段只是标记一下GC Roots
能直接关联到的对象,并且修改TAMS(Next Top at Mark Start)
的值,让下一阶段用户程序并发执行时,能在正确可用的Region
中创建新对象,这个阶段需要停顿线程,但耗时很短。
并发标记是从GC Roots
开始对堆中对象进行可达性分析,找出存活的对象,此阶段耗时较长,但可与用户程序并发执行。
最终标记阶段则是为了修正在并发标记期间用户程序继续运作而导致标记产生变动的那一部分标记记录,虚拟机将这段时间对象变化记录在线程Remembered Set Logs
里面,最终标记阶段需要把Remembered Set Logs
的数据合并到Remembered Set
中,这个阶段需要停顿线程,但是可以并发执行。
筛选回收阶段首先对各个Region
的回收价值和成本进行排行,根据用户所期望的GC
停顿时间来指定回收计划,但是因为只回收一部分Region
,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率,具体执行过程如下图。
5.8 理解 GC 日志
每一种收集器的日志形式都是由它们自身的实现所决定的,也就是说,每个收集器的日志格式都可以不一样。下面看一个典型的GC
日志:
5.617: [GC 5.617: [ParNew: 43296K->7006K(47808K),
0.0136826 secs] 44992K->8702K(252608K), 0.0137904 secs]
[Times: user=0.03 sys=0.00, real=0.02 secs]
说明:
5.617(时间戳): [GC(Young GC,收集器类型) 5.617(时间戳):
[ParNew(使用ParNew作为年轻代的垃圾回收期): 43296K(年轻代垃圾回收前的大小)->
7006K(年轻代垃圾回收以后的大小)(47808K)(年轻代的总大小), 0.0136826 secs(回收时间)]
44992K(堆区垃圾回收前的大小)->8702K(堆区垃圾回收后的大小)(252608K)(堆区总大小),
0.0137904 secs(回收时间)] [Times: user=0.03(Young GC用户耗时)
sys=0.00(Young GC系统耗时), real=0.02 secs(Young GC实际耗时)]
再看一个例子:
7.429: [GC 7.429: [ParNew: 45278K->6723K(47808K),
0.0251993 secs] 46974K->10551K(252608K), 0.0252421 secs]
说明:从GC
记录中我们可以看到Young GC
回收了45278-6723=38555K
的内存。Heap
区通过这次回收总共减少了 46974-10551=36423K
的内存。38555-36423=2132K
说明通过该次Young GC
有2132K
的内存被移动到了Old Gen
。
5.9 垃圾收集器参数总结
参数 | 描述 |
---|---|
-XX:+UseSerialGC |
Jvm 运行在Client 模式下的默认值,打开此开关后,使用Serial + Serial Old 的收集器组合进行内存回收 |
-XX:+UseParNewGC |
打开此开关后,使用ParNew + Serial Old 的收集器进行垃圾回收 |
-XX:+UseConcMarkSweepGC |
使用ParNew + CMS + Serial Old 的收集器组合进行内存回收,Serial Old 作为CMS 出现“Concurrent Mode Failure” 失败后的后备收集器使用 |
-XX:+UseParallelGC |
Jvm 运行在Server 模式下的默认值,打开此开关后,使用Parallel Scavenge + Serial Old 的收集器组合进行回收 |
-XX:+UseParallelOldGC |
使用Parallel Scavenge + Parallel Old 的收集器组合进行回收 |
-XX:SurvivorRatio |
新生代中Eden 区域与Survivor 区域的容量比值,默认为8 ,代表Eden:Subrvivor = 8:1
|
-XX:PretenureSizeThreshold |
直接晋升到老年代对象的大小,设置这个参数后,大于这个参数的对象将直接在老年代分配 |
-XX:MaxTenuringThreshold |
晋升到老年代的对象年龄,每次Minor GC 之后,年龄就加1 ,当超过这个参数的值时进入老年代 |
-XX:UseAdaptiveSizePolicy |
动态调整java 堆中各个区域的大小以及进入老年代的年龄 |
-XX:+HandlePromotionFailure |
是否允许新生代收集担保,进行一次minor gc 后, 另一块Survivor 空间不足时,将直接会在老年代中保留 |
-XX:ParallelGCThreads |
设置并行GC 进行内存回收的线程数 |
-XX:GCTimeRatio |
GC 时间占总时间的比列,默认值为99 ,即允许1% 的GC 时间,仅在使用Parallel Scavenge 收集器时有效 |
-XX:MaxGCPauseMillis |
设置GC 的最大停顿时间,在Parallel Scavenge 收集器下有效 |
-XX:CMSInitiatingOccupancyFraction |
设置CMS 收集器在老年代空间被使用多少后出发垃圾收集,默认值为68% ,仅在CMS 收集器时有效,-XX:CMSInitiatingOccupancyFraction=70
|
-XX:+UseCMSCompactAtFullCollection |
由于CMS 收集器会产生碎片,此参数设置在垃圾收集器后是否需要一次内存碎片整理过程,仅在CMS 收集器时有效 |
-XX:+CMSFullGCBeforeCompaction |
设置CMS 收集器在进行若干次垃圾收集后再进行一次内存碎片整理过程,通常与UseCMSCompactAtFullCollection 参数一起使用 |
-XX:+UseFastAccessorMethods |
原始类型优化 |
-XX:+DisableExplicitGC |
是否关闭手动System.gc()
|
-XX:+CMSParallelRemarkEnabled |
降低标记停顿 |
-XX:LargePageSizeInBytes |
内存页的大小不可设置过大,会影响Perm 的大小,-XX:LargePageSizeInBytes=128m
|
–XX:+UseG1GC |
打开此开关后,使用G1 垃圾收集器 |