不同的GC算法比较

258 阅读6分钟

本文已参与「新人创作礼」活动, 一起开启掘金创作之路。

GC背景

GC:Garage Collector 内存资源有限需要内存共享

循环引用、线程、事务死锁

算法:

  • 引用计数:每次引用次数+1
  • 标记清除算法:遍历所有可达对象(根对象引用的对象),引用跟踪。优点:可解决循环依赖问题。通过压缩或者叫做碎片处理释放内存。STW? 并行GC和CMS的原理。

GC算法:

清除算法:标记清除

复制算法:标记复制:Young区的算法

整理算法:标记清除整理:清理后对不连续的内存空间进行压缩碎片整理

根对象GC Roots

(个人理解:对于线程来说,能够读取到的其实应该是根对象吧)

  • 当前真正执行的方法的局部变量和输入参数

  • 活动线程

  • 所有类的静态字段

  • JNI引用

对象什么时候进入老年代?对象经历了超过阈值(-XX:+MaxTenuringThredshold=15)的年轻代GC后都没有被回收,会复制到老年代

内存泄漏和内存溢出的场景?

分代假设

GC原理:

  • 创建一个新对象放于eden区,标记阶段eden区存活的对应,就会复制到存活区。(为什么是复制而不是移动?每次垃圾回收只有少量的对象存活,直接从from区+eden区复制到to区后,清除from区和eden区)

  • S0和S1,From和To配合eden区实现复制到存活区,对象超过标记阈值后晋升老年代。

  • 老年代默认都是存活的对象,采用移动的方式(因为老年代只有一个区,只能通过移动的方式),标记可达对象,删除不可达对象。整理老年代空间的内存是将所有存活区的对象复制到老年代空间进行依次存放。(对象晋升的概念)。

串行GC和并行GC

串行GC:Serial GC:

-XX:+UseSerialGC配置使用的。

对年轻代使用标记复制算法,对老年代使用标记清除整理算法。两种算法都是单线程的,不并行处理,垃圾回收过程是STW的。

-XX:+UseParNewGC:可以对年轻代进行并行处理

并行GC:Parallel GC

jdk 6\7\8默认GC算法,jdk9默认G1 GC

-XX:UseParallelGC:年轻代和老年代的垃圾回收都会触发STW事件

年轻代使用标记复制算法,老年代使用标记清除整理算法。并行处理的,可以通过-XX:ParallelGCThreads=N指定并行处理GC的线程数量,默认CPU核心线程数。

CMSGC

-XX:+UseConMarkSweepGC:年轻代采用并行的STW方式的标记复制算法,老年代使用并发标记清除算法

CMSGC:避免老年代GC回收时间长造成的长时间卡顿

1、不进行内存压缩碎片处理,使用空闲的free-lists管理内存空间的回收

2、标记清除阶段的大部分工作和应用线程并发呼吸那个,不是STW的方式

默认:CMS使用的并发线程等于核心线程的1/4.

并行Parallel和并发Concurrent区别:

1、并行Parallel GC都是STW的,并行Concurrent只有年轻代是STW

2、Parallel 老年代有压缩碎片整理

3、最大年轻代 :P:1024/3=341.3 CMS:64MGC线程数量13/10=332.8

CMSGC执行的6个阶段(针对老年代)

  1. 初始化标记(所有根对象,根对象引用对象,以及年轻代中存活对象)STW
  2. 并发标记(标记线程和业务线程并发操作的,并发标记状态不是最准确的)
  3. 并发预处理 (把步骤2中的状态变化对象识别出来,通过Card卡片方式将变化区域标记为脏区,卡片标记)
  4. 最终标记 STW(对老年代中所有存活对象进行最终的标记,STW的方式暂停业务线程,处理复杂情况)
  5. 并发清除(删除不再使用的对象,回收内存空间)
  6. 并发重置(重置算法内部数据)

缺点:老年代没有进行内存碎片化问题,会造成不可预测的暂停时间(堆内存空间较大的情况)

JVM内部Rset(Remember Set) 用来记录跨代对象引用关系。

-XX:-UseAdaptiveSizePolicy 禁用自适应策略

G1GC:Garbage-First 垃圾优先

CMSGC的升级

将STW停顿的时间和分布变成可预期且可配置

G1GC中堆不在区分年轻代和老年代,直接划分多个小块:Region,增量方式每次只处理一部分内存块(GC回收集)每次GC暂停会收集年轻代内存块和部分老年代。

并发阶段估算每个小堆存活对象总数

构建回收集的原则:垃圾最多的小块优先收集。

G1GC的配置参数

-XX:+UseG1GC :启用G1

-XX:MaxGCPauseMillis=200 GC暂停的时间,默认200

-XX:G1NewSizePercent :初始化年轻代占比 默认5%

-XX:G1MaxNewSizePercent :最大年轻代占比 默认60%

-XX:G1HeapRegionSize : 小块region的大小 ,单位MB

-XX:ConcGCThreads:应用线程和GC线程的量,默认java线程的1/4.减少数值可能提升并行回收的效率。提高吞吐量

-XX:+InitiationgHeapOccupancyPercent: IHOP,G1内部并行回收循环的启动阈值,默认java heap的45%,当老年代占用大于等于45%,JVM启动内存回收

-XX:G1HeapWastePercent:G1停止回收的最小内存大小,默认5%。GC收集region中对象下降到5%h后停止收集。

-XX:G1MixedGCGountTarget:设置并行循环后需要多少个混合GC启动,默认8。

G1GC异常场景

G1触发FullGC,会退化使用串行收集器完成垃圾清理工作,GC暂停时间将达到秒级别

1、并发模式失败

G1启动标记周期,但在MixGC的时候老年代已经满了,G1放弃标记周期

解决方法:增大堆大小,调整周期

2、晋升失败

没有足够的内存供存活对象或晋升对象使用,触发FullGC

解决办法:

增加-XX:G1ReservePercent选项的值,增加总堆大小,增加预留内存量

较少IHOP提前启动标记周期

增加-XX:ConcGCThreads增加并行标记线程数目

3、巨型对象分配失败

巨型对象找不到合适的空间,触发FullGC

解决办法:

增加内存或者增加小块region的大小

ZGC

-XX:+UnlockExperimentalVMOptions -XX:+UseZGC -Xmx 16G

实验性的GC Java 11

特点:

1、最大GC停顿时间不超过10ms

2、堆内存支持范围广

3、与G1相比,应用吞吐下降不超过15%

4、当前支持Linux/x64平台 JDK15后支持windows和mac

ShennandoahGC

-XX:+UnlockExperimentalVMOptions -XX:+UseShennandoahGC -Xmx 16G

JDK 12

总结

  • 串行GC:单线程执行,应用需要暂停
  • 并行GC:多线程并行执行垃 圾回收,关注高吞吐。业务线程暂停
  • CMS:多线程并发标记和清除,关注于降低延迟
  • G1:通过划分多个内存区域增量整理和回收,进一步降低延迟
  • ZGC:GC暂停时间短,通过着色和读屏障,实现几乎全部并发执行,毫秒级延迟,线程可拓展
  • Epsilon:实验性GC,性能分析用
  • Shennandoah:G1改进版本

常用组合

  • Serial+SerialOld实现单线程的低延迟垃圾回收机制
  • ParNew+CMS实现多线程低延迟垃圾回收机制
  • ParallelScavenge和ParallelScavengeOld实现多线程高吞吐量垃圾回收机制