G1与ZGC垃圾回收器

文章目录

    • G1
      • 概念
      • 垃圾收集过程
      • 垃圾收集分类
      • 关键参数
      • G1垃圾回收器优化建议
    • ZGC
      • 内存布局
      • NUMA
      • 运作过程
      • 颜色指针
      • 读屏障
      • ZGC触发时机
    • 安全点与安全区域

G1

-XX:+UseG1GC



概念

jdk8中也支持G1垃圾回收器,在JDK9时默认使用的是G1垃圾回收器。它适用于堆内存>8G 的场景。

G1垃圾收集器让我们忘记年龄分代的概念,但其实它还是有年龄分代的概念

G1与ZGC垃圾回收器_第1张图片

G1与ZGC垃圾回收器_第2张图片

G1将一整块堆内存,平均分成了默认2048个独立区域Region,JVM目标是不超过2048个Region(JVM源码里TARGET_REGION_NUMBER 定义),实际可以超过该值,但是不推荐。

其中年轻代默认占总堆内存的5%,最大占60%,这其中Eden区域Survivor区的比例还是8:1:1

首先创建对象,对象都是存放在Eden区的Region中,当放满后可能会触发MinorGC,将存活对象使用复制算法移动到Survivor区,然后程序继续运行,存活对象在Survivor区来回复制,当分代年龄达到15后就复制到老年代的Region中。对于大对象直接存放在Humongous区的Region中,当一个对象占用内存大于了Region内存是50%就是大对象。

从上面的执行过程中我们可以发现,每经过一次GC,Region的角色都会动态改变,可能从Old区变为空闲区,再变为Eden区。



垃圾收集过程

G1与ZGC垃圾回收器_第3张图片

和CMS垃圾回收器类似,也有初始标记、并发标记。区别是CMS叫重新标记,G1叫最终标记,但其实是一样的;还有就是G1是筛选回收是STW的,充当了CMS的并发清理和并发重置阶段。

  • 初始标记

  • 并发标记

  • 最终标记

    G1是使用的本地快照SATB的方式来处理漏标问题的

  • 筛选回收

    G1会对要回收的Region进行回收价值和成本计算, 在后台维护一个回收优先级列表。再根据用户期望的GC最大停顿时间( -XX:MaxGCPauseMillis默认200ms)来进行回收内存,在回收时会优先回收存活对象较少的Region,因为如果存活对象少,在采用复制算法将存活对象复制到另一个Region中耗时要少。如果一个Region100的对象,其中95个对象都是存活对象,那么复制的意义就不大,而且复制95个对象很耗时。垃圾回收时如果达到了GC最大停顿时间那么剩余的Region就会等下一次GC再去进行回收。



垃圾收集分类

  • MinorGC

    年轻代默认占总堆内存的5%,其中还有Survivor区,大概Eden区初始占4%左右。当Eden区的Region放满对象后可能就会触发一次MinorGC。

    因为它会计算这一次如果进行MinorGC过程中大概STW的耗时,默认STW最大停顿时间是200ms,如果这一次只需要50ms,那么就不会进行MinorGC,会增加年轻代的region,继续给新对象存放,不会马上做Young GC,直到下一次Eden区放满,如果这一次MinorGC过程中SWT耗时接近200ms了,那么就开始进行MinorGC。

  • MixGC

    当老年代的Region使用率占了总堆内存的45%(默认是45% 靠-XX:InitiatingHeapOccupancyPercent设置),则触发mixGC,回收所有年轻代、部分老年代、部分大对象区。当然这里只是部分的Region区是会根据期望GC停顿时间来确定Region的优先回收顺序。

  • FullGC

    当进行MixGC 使用复制算法,发现没有空闲的Region来支持进行复制时,则触发FullGC

    停止系统程序,然后采用单线程进行标记、清理和压缩整理,好空闲出来一批Region来供下一次MixedGC使用,这个过程是非常耗时的。



关键参数

G1的参数大部分都不建议我们修改,使用默认就可以

# 使用G1垃圾回收器
-XX:+UseG1GC

# 指定GC的线程数
-XX:ParallelGCThreads

# 指定一个Region的大小 1MB~32MB,且必须是2的N次幂 默认是将堆内存平均划分为2048个Region 
-XX:G1HeapRegionSize

# 目标暂停时间,单位毫秒
-XX:MaxGCPauseMillis

# 新生代内存初始空间,默认整堆5%,值配置整数,默认就是百分比
-XX:G1NewSizePercent

# 新生代最大内存空间,默认占堆60%
-XX:G1MaxNewSizePercen

# 对象动态年龄判断,Survivor区的填充容量(默认50%)
# Survivor区域里的一批对象(年龄1+年龄2+年龄n的多个年龄对象)总和超过了Survivor区域的50%,此时就会把年龄n(含)以上的对象都放入老年代
-XX:TargetSurvivorRatio

# 最大分代年龄阀值,默认15
-XX:MaxTenuringThreshold

# 老年代使用率达到了总堆内存阀值则触发MixGC,默认45%
-XX:InitiatingHeapOccupancyPercent

# 默认85%,一个Region中的存活对象低于85%才会回收这个Region,如果存活对象太多了则不回收
-XX:G1MixedGCLiveThresholdPercent

# 在一次回收过程中指定做几次筛选回收(默认8次),在最后一个筛选回收阶段可以回收一会,然后暂停回收,恢复系统运行,一会再开始回收,这样可以让系统不至于单次停顿时间过长。
-XX:G1MixedGCCountTarget

# 默认5%,在进行MixGC 复制存活对象时不断会有空闲Region出来,当空闲Region数量达到总堆内存的5%就结束本次MixGC
-XX:G1HeapWastePercent



G1垃圾回收器优化建议

主要是调整-XX:MaxGCPauseMills参数,如果这个时间设置很大,那么系统运行很久才会触发MinorGC,年轻代可能都占用了堆内存的60%了,然后就会有很多存活对象,这时Survivor区放不下就会进入到老年代区,或者是触发动态年龄判断机制,对象也直接进入到老年代区。

所以这里核心还是在于调节 -XX:MaxGCPauseMills 这个参数的值,在保证他的年轻代gc别太频繁的同时,还得考虑每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc.



ZGC

参考文章:https://wiki.openjdk.java.net/display/zgc/Main

http://cr.openjdk.java.net/~pliden/slides/ZGC-Jfokus-2018.pdf

下面这些JDK版本有ZGC垃圾回收器

G1与ZGC垃圾回收器_第4张图片

G1与ZGC垃圾回收器_第5张图片



内存布局

ZGC收集器是一款基于Region内存布局的, 暂时不设分代的, 使用了读屏障、 颜色指针等技术来实现可并发的标记-整理算法的, 以低延迟为首要目标的一款垃圾收集器。

ZGC的Region可以具有如图3-19所示的大、 中、 小三类容量:

  • 小型Region(Small Region) : 容量固定为2MB, 用于放置小于256KB的小对象。
  • 中型Region(Medium Region) : 容量固定为32MB, 用于放置大于等于256KB但小于4MB的对象。
  • 大型Region(Large Region) : 容量不固定, 可以动态变化, 但必须为2MB的整数倍, 用于放置4MB或以上的大对象。 每个大型Region中只会存放一个大对象, 这也预示着虽然名字叫作“大型Region”, 但它的实际容量完全有可能小于中型Region, 最小容量可低至4MB。 大型Region在ZGC的实现中是不会被重分配(重分配是ZGC的一种处理动作, 用于复制对象的收集器阶段, 稍后会介绍到)的, 因为复制一个大对象的代价非常高昂。

G1与ZGC垃圾回收器_第6张图片



NUMA

UMA即Uniform Memory Access Architecture,所有cpu都去访问同一块内存区域,可能会频繁产生争抢

NUMA就是Non Uniform Memory Access Architecture。将某一块内存区域有限分配给某个cpu去使用

G1与ZGC垃圾回收器_第7张图片



运作过程

G1与ZGC垃圾回收器_第8张图片

其中向下的黑色箭头是STW过程

  • 并发标记Comcurrent Mark

    包括初始标记和最终标记,也是可达性分析算法,与G1和CMS不同的是,ZGC不是标记在对象头中,而是标记在对象的引用指针的。

    标记阶段会更新颜色指针

  • 并发预备重分配Concurrent Prepare for Reloc

    统计要清理的Region,组成一个重分配集Relocation Set。

    ZGC每次都会去扫描所有的Region,用更大的扫描成本去换取G1中记忆集的维护成本

  • 并发重分配Concurrent Relocate

    把重分配集Relocation Set中的存活对象复制到新的Region中,并为重分配集中每个Region维护一个转发表Forward Table。记录旧对象地址到新地址的转向关系。

    如果用户线程要读取对象被复制到了一个新的Region中去了,从当前旧的地址引用中我们就能知道一个对象是否在重分配集中,如果在则会经过读屏障 --> 转发表 -->新地址引用,并同时修正该引用的值使其执行对象的新地址,ZGC讲这种称为指针自愈

  • 并发重映射Concurrent Remap

    重映射所做的就是修正整个堆中指向重分配集中旧对象的所有引用。因为ZGC有指针自愈的功能,就将并发重映射阶段合并到下一次并发标记阶段了,因为他们都需要遍历所有对象



颜色指针

Colored Pointers,即颜色指针,如下图所示,ZGC的核心设计之一。以前的垃圾回收器的GC信息都保存在对象头中,而ZGC的GC信息保存在指针中。

G1与ZGC垃圾回收器_第9张图片

64位的指针不需要全部用来寻址,取其中的42位来进行寻址就可以了,42位就能够满足4TB内存的寻址

每个对象有一个64位指针,这64位被分为:

  • 18位:预留给以后使用;
  • 1位:Finalizable标识,此位与并发引用处理有关,它表示这个对象只能通过finalizer才能访问;
  • 1位:Remapped标识,设置此位的值后,对象未指向relocation set中(relocation set表示需要GC的Region集合);
  • 1位:Marked1标识;
  • 1位:Marked0标识,和上面的Marked1都是标记对象用于辅助GC;
  • 42位:对象的地址(所以它可以支持2^42=4T内存)

为什么有2个mark标记?

每一个GC周期开始时,会交换使用的标记位,使上次GC周期中修正的已标记状态失效,所有引用都变成未标记。

GC周期1:使用mark0, 则周期结束所有引用mark标记都会成为01。

GC周期2:使用mark1, 则期待的mark标记10,所有引用都能被重新标记。

通过对配置ZGC后对象指针分析我们可知,对象指针必须是64位,那么ZGC就无法支持32位操作系统,同样的也就无法支持压缩指针了(CompressedOops,压缩指针也是32位)。



读屏障

之前的垃圾收集器是采用的写屏障,而ZGC使用的是读屏障

在标记和移动对象阶段,从堆中读取一个对象时会经过读屏障,根据这个对象的地址引用来判断这个指针是Bad Color还是Good Color,如果是Bad Color读屏障则把读出的指针更新到对象的新地址上,并且把堆里的这个指针“修正”到原本的字段里。如果指针是Good Color,那么正常往下执行即可

判断对象是Bad Color还是Good Color的依据是什么呢?就是根据上一段提到的Colored Pointers的4个颜色位。当加上读屏障时,根据对象指针中这4位的信息,就能知道当前对象是Bad/Good Color了。



ZGC触发时机

ZGC目前有4中机制触发GC:

  • 定时触发,默认为不使用,可通过ZCollectionInterval参数配置。
  • 预热触发,最多三次,在堆内存达到10%、20%、30%时触发,主要时统计GC时间,为其他GC机制使用。
  • 分配速率,基于正态分布统计,计算内存99.9%可能的最大分配速率,以及此速率下内存将要耗尽的时间点,在耗尽之前触发GC(耗尽时间 - 一次GC最大持续时间 - 一次GC检测周期时间)。
  • 主动触发,(默认开启,可通过ZProactive参数配置) 距上次GC堆内存增长10%,或超过5分钟时,对比距上次GC的间隔时间跟(49 * 一次GC的最大持续时间),超过则触发。



安全点与安全区域

安全点就是指代码中一些特定的位置,当线程运行到这些位置时它的状态是确定的,这样JVM就可以安全的进行一些操作,比如GC等,所以GC不是想什么时候做就立即触发的,是需要等待所有线程运行到安全点后才能触发。

这些特定的安全点位置主要有以下几种:

  1. 方法返回之前
  2. 调用某个方法之后
  3. 抛出异常的位置
  4. 循环的末尾

大体实现思想是当垃圾收集需要中断线程的时候, 不直接对线程操作, 仅仅简单地设置一个标志位, 各个线程执行过程时会不停地主动去轮询这个标志, 一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。 轮询标志的地方和安全点是重合的。



安全区域又是什么?

Safe Point 是对正在执行的线程设定的。

如果一个线程处于 Sleep 或中断状态,它就不能响应 JVM 的中断请求,再运行到 Safe Point 上。

因此 JVM 引入了 Safe Region。

Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的。

你可能感兴趣的:(报班总结笔记,jvm,java,开发语言)