特点
整代收集:G1 是同时回收新生代和老年代的,不需要搭配使用。
可以设定停顿时间:G1 最大的特点就是可以设置一个预期的停顿时间,例如希望 G1 回收器在 1 小时内的 Stop the world 时间小于 1 分钟。
G1 会追踪每个 Region 的回收价值,例如回收这个 Region 需要耗费多长时间,可以回收掉多少垃圾。回收的时候,尽可能的选择回收时间短,可回收的垃圾多的 Region 进行回收。
G1 的内存分配和回收
G1 收集器是将堆内存划分为大小相等的 Region 进行管理。有的 Region 是新生代,有的 Region 是老年代。
到底有多少个 Region,每个 Region 多大?
默认情况下,堆内存最多有 2048 个 Region 。每个 Region 的大小就=堆内存的总大小 / 2048 。Region 的大小必须是 2 的倍数。 可以通过参数 -XX:G1HeapRegionSize 来指定每个 Region 的大小。
Region 最小 1M ,最大可以是 32M 。不能超过 32M 是考虑到回收时候的性能。
看到资料最多是可以超过 2048 个Region 的。 内存设置为 128G ,Region 设置为 32M ,就是 4096 个 Region。 G1 的 Region 是如何划分数量和大小的?
新生代内存分配
JVM 启动的时候,划分了很多 Region ,但是没有还没区分这些 Region 是怎么使用。
默认新生代占堆内存的 5%。可以通过参数 -XX:G1NewSizePercent 设置。在不断的内存使用中,新生代 Region 是会增长的,但是默认最多不会超过 60% 。可以通过参数 -XX:G1MaxNewSizePercent 设置。
在新生代里面,还是会有 Eden 区和 Survivor 区的区分的。只是改成用 Region 来组成。可以通过参数
-XX:SurvivorRatio 来配置新生代和老年代的比例。
新生代的垃圾回收
随着系统的运行,新生代的 Region 达到了堆内存的 60% ,就会触发新生代的回收。使用复制算法,将存活的对象转移到 S 区,然后根据评估的回收价值,对 Eden 区的 Region 进行回收。
但是这个过程和之前的新生代收集器是有差别的。这个过程,会根据参数 -XX:MaxGCPauseMills 设置的停顿时间来进行 Stop the world。默认是 200ms 。这个 STW 过程会尽可能保证在设定的停顿时间范围内。
新生代进入老年代的规则
- 到达老年代年龄
- 动态年龄判断机制。(还不是非常清晰,需要去做 GC 实验去验证)
- 存活的对象,超过 S 区的容量,全部进入老年代。
大对象 Region
G1 的内存模型里面,大对象不会进入老年代,而是有专门的 Region 来存储大对象。G1 的大对象是指超过 Region 大小的一半的对象才是大对象。如果一个 Region 装不下,还可以横跨多个 Region 来存储。
大对象不属于新生代,也不属于老年代。大对象会在进行新生代、老年代回收的时候,顺便一起回收了。
什么时候触发混合回收?
老年代的 Region 的使用率超过一定的比例,就会触发混合回收,新生代 + 老年代一起回收。默认是 45%。由参数 -XX:InitiatingHeapOccupancyPercent 进行配置。
什么时候停止回收?
在混合回收的时候,使用的是复制算法,所以就会不断有新的 Region 被清空,空闲出来。当空闲的 Region 达到堆内存的 5% ,就会停止混合回收。由参数 -XX:G1HeapWasterPercent 进行控制,默认5%
这个有疑问,那么,默认情况下,老年代占用比例到达 45% 就会进行回收,空闲 5% 就停止回收了。那不就白忙活了?
回收哪些 Region ?
默认情况下,存活对象低于 Region 容量的 85% 的 Region 才会被回收。存活对象过多的 Region 没必要将它来回复制到其他 Region 浪费性能。 可以由参数 -XX:G1MixGCLiveThresholdPercent 控制,默认是 85%。
回收过程
初始标记
先停止所有线程的运行,仅仅是标记出 GCRoot 能关联到的对象。速度很快,而且这个过程还是借助在 Minor GC 的时候完成的。所以几乎不会造成额外的停顿。
并发标记
用户线程和垃圾回收线程同时运行,进行 GC Root 追踪,追踪所有存活的对象。虽然这个阶段很耗时,但是由于是并发执行的,所以对系统的影响也不大。
最终标记
进入 Stop the World ,根据并发标记阶段的对象的修改,最终标记一下有哪些对象是存活的,哪些是垃圾。
筛选回收
进入 Stop the world ,根据对 Region 的评估和用户设定的停顿时间,根据回收价值由大到小进行 Region 的回收。不一定能全部回收完,但是保证了在用户设定的停顿时间内结束。
在进行最后一个回收步骤的时候,G1 允许回收行为分为多次进行。过程是,先进行 STW 回收掉一部分的 Region 然后然系统继续运行,然后再 STW 进行回收,然后再运行。这个回收的次数可以由参数
-XX:G1MixedGCCountTarget 进行配置,默认是 8 次。这样的设计也是为了尽量减少系统的停顿时间。但是是否也增加了线程切换的成本?用户线程切换成垃圾回收线程?
G1 会根据设置的的 GC 停顿时间,给新生代分配一些 Region ,到一定的程度就会触发 GC ,并且把 GC 时间控制在预设范围之内,尽量避免一次性回收过多的 Region 导致 GC 停顿时间超出预期。
G1 适合大内存的机器。 因为可以控制停顿时间。 如果用 ParNew + CMS ,大内存的机器产生一次 Full GC 的话,停顿时间就会特别长。