「JVM」如何看待G1

315 阅读3分钟

一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第19天,点击查看活动详情

G1 区域分代化

为什么有G1?

随着业务的发展,内存和处理器的优化。进而在延迟可控的情况下提升高吞吐量,实现一个全功能的收集器(新生代和老年代)

工作模式

垃圾优先(Garbage First)

1、将整个堆分成不同的Region。并将其分配给Eden、Survivor0、Survivor1、老年代。

2、G1监控每个Region中垃圾堆积(回收能释放的空间和回收所需要的时间等)。在后台维护一个优先列表

3、每次需要回收的时候,根据允许的收集时间来计算出合理的回收方式使回收能获取收益最大的Region。

优势

  • 并行和并发:

    并行:多个CPU可以一起进行GC来加快GC的速度。会短暂STW

    并发:有些操作可以和用户线程一起执行。

  • 分代收集:

    虽然还是分成老年代和年轻代。但是已经和之前的分代不一样,每个区域都可以不是连续的空间了。而是划分成不同的Region

  • 空间整合:

    空间上因为是分区,每次回收最小单位都是Region。所以针对每个Region的回收可以是复制算法。而整个大的堆可以看成是标记整理算法

  • 可限制的停顿时间:

    使用者可以指定在某个时间范围内,消耗在GC上的时间不超过多少

缺点:

  1. 相对于CMS,G1比不上最好回收时间的CMS。但是整体来看会更优一些
  2. 从经验上来看,更大的内存G1的性能就更好,平衡点在6~8GB。

参数设置

  • -XX:UseG1GC,设置使用G1
  • -XX:G1HeapRegionSize:设置每个Region的大小。需要是2的N次幂。一般1~32M
  • -XX:MaxGCPauseMillis设置期望达到的最大GC停顿时间指标,默认值是200ms
  • -XX:ParallelGCThread设置STW工作线程数的值,最大的是8
  • -XX:ConcGCThreads:设置并发标记的线程数。将 n 设置为并行垃圾回收线程数(ParallelGCThreads)的 1/4 左右。
  • -XX:InitiatingHeapOccupancyPercent:设置触发并发 GC 周期的 Java 堆占用率阈值。超过此值,就触发 GC,默认值是 45。

调优方式:

  • 开启G1垃圾回收器
  • 设置堆的最大内存
  • 设置最大的允许停顿时间
  • 测试来检测性能

JDK7开始允许正常使用,JDK9及其之后都默认使用。

分析

  1. G1将整个堆划分成约2048个Region,每个Region的大小都是相同的,根据参数设置可能是1M、2M、4M、8M、16M、32M。每个Region只有一个角色(空了之后可以转变成其他角色)
  2. 如果一个大对象的大小超过了1.5Region,就会被保存在Humongous中。如果一个H保存不下一个大对象,会找一个连续的H区域来存储。这个时候有可能会触发Full GC

Remembered Set记忆集,记录当前Region被哪些Region所引用,免除了YoungGC回收的时候还需要遍历Old区等情况

为了解决,堆中不同Region相互引用的时候(Eden被Old引用),进行YoungGC只回收Eden中的对象,但是这个对象被Old区引用着。不知道能不能回收。就又需要去检查Old区。

RSet就是记录每个Region中被其他Region的引用信息。所以回收的时候,就只用看看RSet中的引用信息就好了。

回收过程

回收过程包括四个环节

  1. 年轻代GC(Young GC(MinorGC))并行独占
  2. 年轻代GC+老年代并发标记过程(当堆内存使用达到一定值(默认 45%)时,开始老年代并发标记过程。)
  3. 混合回收(Mixed GC)
  4. Full GC也存在(单线程,独占式),提供一种GC的保护机制防止OOM

每个环节的具体实现大佬的blog