JVM-垃圾收集器G1

G1垃圾回收器

概述:

  • 是一款面向服务器的垃圾收集器,主要针对配备多个处理器及大容量内存的机器.
  • 以极高效率满足GC停顿时间要求的同时,还具备高吞吐量性能特征.
  • G1保留了年轻代和老年代的概念,但不再是物理隔阂了,它们都是(可以不连续)Region的集合。


特点:

  • 使用参数:G1垃圾收集器:-XX:+UseG1GC
  • 将Java堆内存 拆分成很多大小相等的Region。每个Region大小计算规则?
    • Region 基于堆大小默认计算。(堆大小设置:-Xms -Xmx)
      • 计算方式: 如 4G堆大小 ,每个Region的大小就是2MB=4096MB/2048个Region。
      • 规则:Region 必须是2的倍数 如1MB 2MB...
      • 手动调整:-XX:G1HeapRegionSize
  • Region的区域功能 可能会动态变化
    • 一个Region可能之前是年轻代,如果Region进行了垃圾回收,之后可能又会变成老年代。
  • 可设置垃圾回收预期时间。
  • 目的: 尽量把垃圾回收时间控制在一个指定的时间范围内。
    G1在各个分代特性
    新生代:
  • 初始新生代对堆内存的占比5%。(eg:4G堆内存 占200M、约100Region)
    • 新生代占比调整:XX:G1NewSizePercent (正常情况维持默认即可)
  • 依旧存在Edgen和Survivor的概念
    • 新生代、老年代区分各自占据不同Region
    • 新生代Region区分可通过 -XX:SurvivorRatio=8 比例区分
      • 如:100个Region,可能80个Region就是Eden,两个Survivor各自占10个Region。
  • 当对象不停的在新生代里分配,新生代的Region会不断增加,Eden和Survivor对应的Region也会不断增加。
    老年代:
  • G1的内存模型下,新生代(max=60%)和老年代各自都会占据一定的Region。(max=40%)
  • 对象什么时候进去老年代?(规则同CMS进入老年代不同在于大对象的处理)
    • 达到一定年龄(对象在新生代躲过了很多次的垃圾回收)
      • "-XX:MaxTenuringThreshold"参数可以设置这个年龄,他就会进入老年代
    • 动态年龄判定规则
      • 发现某次新生代GC过后,存活对象超过了Survivor的50%
      • 判断eg:年龄为1岁,2岁,3岁,4岁的对象的大小总和超过Survivor的50%,
  • 此时4岁以上的对象全部会进入老年代,这就是动态年龄判定规则。
    G1内存模型下对大对象的分配和回收的策略
  • G1垃圾收集器对于对象什么时候会转移到老年代跟之前讲过的原则一样,唯一不同的是对大对象的处理,
  • G1有专门分配 大对象的Region叫Humongous区,而不是让大对象直接进入老年代的Region中。
  • 在G1中,大对象的判定规则就是一 个大对象超过了一个Region大小的50%,比如按照上面算的,每个Region是2M,只要一个大对象超过了1M,就会被放 入Humongous中。
  • 一个大对象如果太大,可能会横跨多个Region来存放。 Humongous区专门存放短期巨型对象,不用直接进老年代,可以节约老年代的空间,避免因为老年代空间不够的GC开 销。
  • Full GC的时候除了收集年轻代和老年代之外,也会将Humongous区一并回收。

G1收集器一次GC的运作过程

初始标记(initial mark,STW):

  • 暂停所有的其他线程,并记录下gc roots直接能引用的对象,速度很快 ;
    并发标记(Concurrent Marking):
  • 同CMS的并发标记
    最终标记(Remark,STW):
  • 同CMS的重新标记
    筛选回收(Cleanup,STW):
  • 筛选回收阶段首先对各个Region的回收价值和成本进行排序,根据用户所期 望的GC停顿时间(可以用JVM参数 -XX:MaxGCPauseMillis指定)来制定回收计划,
    • 比如说老年代此时有1000个 Region都满了,但是因为根据预期停顿时间,本次垃圾回收可能只能停顿200毫秒,那么通过之前回收成本计算得 知,可能回收其中800个Region刚好需要200ms,那么就只会回收800个Region(Collection Set,要回收的集 合),尽量把GC导致的停顿时间控制在我们指定的范围内。
  • 这个阶段其实也可以做到与用户程序一起并发执行,但 是因为只回收一部分Region,时间是用户可控制的,而且停顿用户线程将大幅提高收集效率。
  • 不管是年轻代或是老 年代,回收算法主要用的是复制算法,将一个region中的存活对象复制到另一个region中,这种不会像CMS那样 回收完因为有很多内存碎片还需要整理一次,G1采用复制算法回收几乎不会有太多内存碎片。(注意:CMS回收阶 段是跟用户线程一起并发执行的,G1因为内部实现太复杂暂时没实现并发回收,不过到了Shenandoah就实现了并 发收集,Shenandoah可以看成是G1的升级版本)

G1高效回收实现

  • G1收集器在后台维护了一个优先列表,每次根据允许的收集时间,优先选择回收价值最大的Region(这也就是它的名字Garbage-First的由来),
    • 比如一个Region花200ms能回收10M垃圾,另外一个Region花50ms能回收20M垃圾,在回收时间有限情况下,G1当然会优先选择后面这个Region回收。
  • 这种使用Region划分内存空间以及有优先级的区域回收方式,保证了G1收集器在有限时间内可以尽可能高的收集效率。

G1的重要特性

并行与并发 :

  • G1能充分利用CPU、多核环境下的硬件优势,使用多个CPU(CPU或者CPU核心)来缩短Stop-The-World停顿时间。部分其他收集器原本需要停顿Java线程来执行GC动作,G1收集器仍然可以通过并发的方式让java程序继续执行。
    分代收集 :
  • 虽然G1可以不需要其他收集器配合就能独立管理整个GC堆,但是还是保留了分代的概念。
    空间整合 :
  • 与CMS的"标记--清理"算法不同,G1从整体来看是基于"标记整理"算法实现的收集器;从局部上来看是基于"复制"算法实现的。
    可预测的停顿 :
  • 这是G1相对于CMS的另一个大优势,降低停顿时间是G1 和 CMS 共同的关注点,
  • 但G1 除了追求低停顿外,还能建立可预测的停顿时间模型,能让使用者明确指定在一个长度为M毫秒的时间片段(通过参数"- XX:MaxGCPauseMillis"指定)内完成垃圾收集
    毫无疑问, 可以由用户指定期望的停顿时间是G1收集器很强大的一个功能, 设置不同的期望停顿时间, 可使得G1在不 同应用场景中取得关注吞吐量和关注延迟之间的最佳平衡。 不过, 这里设置的"期望值"必须是符合实际的, 不能异想 天开, 毕竟G1是要冻结用户线程来复制对象的, 这个停顿时 间再怎么低也得有个限度。 它默认的停顿目标为两百毫秒, 一般来说, 回收阶段占到几十到一百甚至接近两百毫秒都很 正常, 但如果我们把停顿时间调得非常低, 譬如设置为二十毫秒, 很可能出现的结果就是 由于停顿目标时间太短, 导 致每次选出来的回收集只占堆内存很小的一部分, 收集器收集的速度逐渐跟不上分配器分配的速度, 导致垃圾慢慢堆 积。 很可能一开始收集器还能从空闲的堆内存中获得一些喘息的时间, 但应用运行时间一长就不行了, 最终占满堆引发 Full GC反而降低性能, 所以通常把期望停顿时间设置为一两百毫秒或者两三百毫秒会是比较合理的。

G1垃圾收集分类

YoungGC

  • YoungGC并不是说现有的Eden区放满了就会马上触发,G1会计算下现在Eden区回收大概要多久时间,如果回收时 间远远小于参数 -XX:MaxGCPauseMills 设定的值,那么增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,G1计算回收时间接近参数 -XX:MaxGCPauseMills 设定的值,那么就会触发Young GC
    MixedGC
  • 不是FullGC,老年代的堆占有率达到参数(-XX:InitiatingHeapOccupancyPercent)设定的值则触发,回收所有的 Young和部分Old(根据期望的GC停顿时间确定old区垃圾收集的优先顺序)以及大对象区,
  • 正常情况G1的垃圾收集是先做 MixedGC,主要使用复制算法,需要把各个region中存活的对象拷贝到别的region里去,拷贝过程中如果发现没有足够 的空region能够承载拷贝对象就会触发一次Full GC
    Full GC
  • 停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这 个过程是非常耗时的。(Shenandoah优化成多线程收集了)

G1垃圾收集器优化建议

  • 假设参数 -XX:MaxGCPauseMills 设置的值很大,导致系统运行很久,年轻代可能都占用了堆内存的60%了,此时才 触发年轻代gc。 那么存活下来的对象可能就会很多,此时就会导致Survivor区域放不下那么多的对象,就会进入老年代中。
  • 或者是你年轻代gc过后,存活下来的对象过多,导致进入Survivor区域后触发了动态年龄判定规则,达到了Survivor 区域的50%,也会快速导致一些对象进入老年代中。
  • 所以这里核心还是在于调节 -XX:MaxGCPauseMills 这个参数的值,在保证他的年轻代gc别太频繁的同时,还得考虑 每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc.

什么场景适合使用G1

  1. 50%以上的堆被存活对象占用
  2. 对象分配和晋升的速度变化非常大
  3. 垃圾回收时间特别长,超过1秒
  4. 8GB以上的堆内存(建议值)
  5. 停顿时间是500ms以内
相关推荐
风象南1 小时前
SpringBoot中6种自定义starter开发方法
java·spring boot·后端
mghio10 小时前
Dubbo 中的集群容错
java·微服务·dubbo
咖啡教室15 小时前
java日常开发笔记和开发问题记录
java
咖啡教室15 小时前
java练习项目记录笔记
java
鱼樱前端16 小时前
maven的基础安装和使用--mac/window版本
java·后端
RainbowSea16 小时前
6. RabbitMQ 死信队列的详细操作编写
java·消息队列·rabbitmq
RainbowSea16 小时前
5. RabbitMQ 消息队列中 Exchanges(交换机) 的详细说明
java·消息队列·rabbitmq
算AI17 小时前
人工智能+牙科:临床应用中的几个问题
人工智能·算法
李少兄18 小时前
Unirest:优雅的Java HTTP客户端库
java·开发语言·http