-XX:+UseG1GC
jdk8中也支持G1垃圾回收器,在JDK9时默认使用的是G1垃圾回收器。它适用于堆内存>8G 的场景。
G1垃圾收集器让我们忘记年龄分代的概念,但其实它还是有年龄分代的概念
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区。
和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
主要是调整-XX:MaxGCPauseMills
参数,如果这个时间设置很大,那么系统运行很久才会触发MinorGC,年轻代可能都占用了堆内存的60%了,然后就会有很多存活对象,这时Survivor区放不下就会进入到老年代区,或者是触发动态年龄判断机制,对象也直接进入到老年代区。
所以这里核心还是在于调节 -XX:MaxGCPauseMills 这个参数的值,在保证他的年轻代gc别太频繁的同时,还得考虑每次gc过后的存活对象有多少,避免存活对象太多快速进入老年代,频繁触发mixed gc.
参考文章:https://wiki.openjdk.java.net/display/zgc/Main
http://cr.openjdk.java.net/~pliden/slides/ZGC-Jfokus-2018.pdf
下面这些JDK版本有ZGC垃圾回收器
ZGC收集器是一款基于Region内存布局的, 暂时不设分代的, 使用了读屏障、 颜色指针等技术来实现可并发的标记-整理算法的, 以低延迟为首要目标的一款垃圾收集器。
ZGC的Region可以具有如图3-19所示的大、 中、 小三类容量:
UMA即Uniform Memory Access Architecture,所有cpu都去访问同一块内存区域,可能会频繁产生争抢
NUMA就是Non Uniform Memory Access Architecture。将某一块内存区域有限分配给某个cpu去使用
其中向下的黑色箭头是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信息保存在指针中。
64位的指针不需要全部用来寻址,取其中的42位来进行寻址就可以了,42位就能够满足4TB内存的寻址
每个对象有一个64位指针,这64位被分为:
为什么有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目前有4中机制触发GC:
安全点就是指代码中一些特定的位置,当线程运行到这些位置时它的状态是确定的,这样JVM就可以安全的进行一些操作,比如GC等,所以GC不是想什么时候做就立即触发的,是需要等待所有线程运行到安全点后才能触发。
这些特定的安全点位置主要有以下几种:
大体实现思想是当垃圾收集需要中断线程的时候, 不直接对线程操作, 仅仅简单地设置一个标志位, 各个线程执行过程时会不停地主动去轮询这个标志, 一旦发现中断标志为真时就自己在最近的安全点上主动中断挂起。 轮询标志的地方和安全点是重合的。
安全区域又是什么?
Safe Point 是对正在执行的线程设定的。
如果一个线程处于 Sleep 或中断状态,它就不能响应 JVM 的中断请求,再运行到 Safe Point 上。
因此 JVM 引入了 Safe Region。
Safe Region 是指在一段代码片段中,引用关系不会发生变化。在这个区域内的任意地方开始 GC 都是安全的。