# 垃圾回收器及常用参数

# 垃圾收集器

# 常见的垃圾回收器

常用垃圾回收器

  1. JDK诞生 Serial追随 提高效率,诞生了PS,为了配合CMS,诞生了PN,CMS是1.4版本后期引入,CMS是里程碑式的GC,它开启了并发回收的过程,但是CMS毛病较多,因此目前没有任何一个JDK版本默认是CMS 并发垃圾回收是因为无法忍受STW(Stop The World)
  2. Serial 年轻代 串行回收
  3. PS 年轻代 并行回收
  4. ParNew 年轻代 配合CMS的并行回收
  5. SerialOld
  6. ParallelOld
  7. ConcurrentMarkSweep 老年代 并发的, 垃圾回收和应用程序同时运行,降低STW的时间(200ms) CMS问题比较多,所以现在没有一个版本默认是CMS,只能手工指定 CMS既然是MarkSweep,就一定会有碎片化的问题,碎片到达一定程度,CMS的老年代分配对象分配不下的时候,使用SerialOld 进行老年代回收 想象一下: PS + PO -> 加内存 换垃圾回收器 -> PN + CMS + SerialOld(几个小时 - 几天的STW) 几十个G的内存,单线程回收 -> G1 + FGC 几十个G -> 上T内存的服务器 ZGC 算法:三色标记 + Incremental Update
  8. G1(10ms) 算法:三色标记 + SATB
  9. ZGC (1ms) PK C++ 算法:ColoredPointers + LoadBarrier
  10. Shenandoah 算法:ColoredPointers + WriteBarrier
  11. Eplison
  12. PS 和 PN区别的延伸阅读: https://docs.oracle.com/en/java/javase/13/gctuning/ergonomics.html#GUID-3D0BB91E-9BFF-4EBB-B523-14493A860E73
  13. 垃圾收集器跟内存大小的关系
    1. Serial 几十兆
    2. PS 上百兆 - 几个G
    3. CMS - 20G
    4. G1 - 上百G
    5. ZGC - 4T - 16T(JDK13)

1.8默认的垃圾回收:PS + ParallelOld

# Serial垃圾收集器

  • JDK1.3之前新生代唯一的垃圾收集器
  • 单线程, 复制算法
  • 收集时必须停止其他所有的工作线程

Serial(连续的)是最基本垃圾收集器,使用复制算法,曾经是JDK1.3.1 之前新生代唯一的垃圾收集器。Serial 是一个单线程的收集器,它不但只会使用一个CPU 或一条线程去完成垃圾收集工作,并且在进行垃圾收集的同时,必须暂停其他所有的工作线程,直到垃圾收集结束。

Serial 垃圾收集器虽然在收集垃圾过程中需要暂停所有其他的工作线程,但是它简单高效,对于限定单个CPU 环境来说,没有线程交互的开销,可以获得最高的单线程垃圾收集效率,因此Serial垃圾收集器依然是java 虚拟机运行在 Client 模式下默认的新生代垃圾收集器。

# Parallel Scavenge 收集器

  • 多线程复制算法
  • 需要暂停工作线程
  • 高效率
  • 程序达到一个可控制的吞吐量
  • 自适应调节策略

Parallel Scavenge 收集器也是一个新生代垃圾收集器,同样使用复制算法,也是一个多线程的垃圾收集器,它重点关注的是程序达到一个可控制的吞吐量(Thoughput,CPU 用于运行用户代码的时间/CPU 总消耗时间,即吞吐量=运行用户代码时间/(运行用户代码时间+垃圾收集时间)),

高吞吐量可以最高效率地利用CPU 时间,尽快地完成程序的运算任务,主要适用于在后台运算而不需要太多交互的任务。自适应调节策略也是ParallelScavenge 收集器与ParNew 收集器的一个重要区别。

# ParNew垃圾收集器

  • Serial + 多线程
  • 收集时也要暂停其他所有工作线程

ParNew(平行的) 垃圾收集器其实是Serial 收集器的多线程版本,也使用复制算法,除了使用多线程进行垃圾收集之外,其余的行为和Serial 收集器完全一样,ParNew 垃圾收集器在垃圾收集过程中同样也要暂停所有其他的工作线程。

ParNew 收集器默认开启和 CPU 数目相同的线程数,可以通过 -XX:ParallelGCThreads 参数来限制垃圾收集器的线程数。

ParNew虽然是除了多线程外和Serial 收集器几乎完全一样,但是 ParNew 垃圾收集器是很多 Java 虚拟机运行在 Server 模式下新生代的默认垃圾收集器

# Serial Old 收集器

  • 单线程, 标记整理算法
  • 需要暂停工作线程
  • 老年代收集器

Serial Old 是Serial 垃圾收集器年老代版本,它同样是个单线程的收集器,使用标记-整理算法,这个收集器也主要是运行在Client 默认的java 虚拟机默认的年老代垃圾收集器。

在Server 模式下,主要有两个用途:

  1. JDK1.5 之前版本中与新生代的 Parallel Scavenge 收集器搭配使用。作为年老代中使用 CMS 收集器的后备垃圾收集方案。新生代 Serial 与年老代 Serial Old 搭配垃圾收集过程图:

img

  1. 新生代 Parallel Scavenge 收集器与 ParNew 收集器工作原理类似,都是多线程的收集器,都使用的是复制算法,在垃圾收集过程中都需要暂停所有的工作线程。新生代Parallel Scavenge/ParNew 与年老代Serial Old 搭配垃圾收集过程图:

img

# Parallel Old 收集器

  • 多线程标记整理算法
  • 需要暂停工作线程
  • Parallel Scavenge 的年老代版本
  • JDK1.6开始提供

Parallel Old 收集器是Parallel Scavenge 的年老代版本,使用多线程的标记-整理算法,在JDK1.6才开始提供。

在JDK1.6 之前,新生代使用ParallelScavenge 收集器只能搭配年老代的Serial Old 收集器,只能保证新生代的吞吐量优先,无法保证整体的吞吐量,Parallel Old 正是为了在年老代同样提供吞吐量优先的垃圾收集器,如果系统对吞吐量要求比较高,可以优先考虑新生代Parallel Scavenge和年老代Parallel Old 收集器的搭配策略。

新生代Parallel Scavenge 和年老代Parallel Old 收集器搭配运行过程图:

img

# CMS 收集器

  • 老年代收集器
  • 获取最短垃圾回收停顿时间
  • 多次按此标记-清除算法
  • 初始标记时及重新标记时需要暂停所有工作线程.
  • 会产生浮动垃圾(清理过程中产生的垃圾)

多线程标记清除算法 (Concurrent mark sweep - CMS) 收集器是一种年老代垃圾收集器,其最主要目标是获取最短垃圾回收停顿时间,和其他年老代使用标记-整理算法不同,它使用多线程的标记-清除算法。最短的垃圾收集停顿时间可以为交互比较高的程序提高用户体验。

# 执行阶段

CMS 工作机制相比其他的垃圾收集器来说更复杂,整个过程分为以下4个阶段:

  1. 初始标记只是标记一下 GC Roots 能直接关联的对象,速度很快,仍然需要暂停所有的工作线程。

    image-20201116145826846

  2. 并发标记进行 GC Roots 跟踪的过程,和用户线程一起工作,不需要暂停工作线程。

  3. 重新标记为了修正在并发标记期间,因用户程序继续运行而导致标记产生变动的那一部分对象的标记记录,仍然需要暂停所有的工作线程。

  4. 并发清除, 清除 GC Roots 不可达对象,和用户线程一起工作,不需要暂停工作线程。

由于耗时最长的并发标记和并发清除过程中,垃圾收集线程可以和用户现在一起并发工作,所以总体上来看CMS 收集器的内存回收和用户线程是一起并发地执行。

CMS 收集器工作过程:

img

# CMS的问题

  1. 内存碎片化(Memory Fragmentation)

    -XX:+UseCMSCompactAtFullCollection -XX:CMSFullGCsBeforeCompaction 默认为0 指的是经过多少次FGC才进行压缩

  2. 浮动垃圾(Floating Garbage)

    Concurrent Mode Failure 产生:if the concurrent collector is unable to finish reclaiming the unreachable objects before the tenured generation fills up, or if an allocation cannot be satisfiedwith the available free space blocks in the tenured generation, then theapplication is paused and the collection is completed with all the applicationthreads stopped

    解决方案:降低触发CMS的阈值

    PromotionFailed

    解决方案类似,保持老年代有足够的空间

    –XX:CMSInitiatingOccupancyFraction 92% 可以降低这个值,让CMS保持老年代足够的空间

# CMS日志分析

执行命令:java -Xms20M -Xmx20M -XX:+PrintGCDetails -XX:+UseConcMarkSweepGC com.mashibing.jvm.gc.T15_FullGC_Problem01

[GC (Allocation Failure) [ParNew: 6144K->640K(6144K), 0.0265885 secs] 6585K->2770K(19840K), 0.0268035 secs] [Times: user=0.02 sys=0.00, real=0.02 secs]

ParNew:年轻代收集器

6144->640:收集前后的对比

(6144):整个年轻代总容量

6585 -> 2770:整个堆的情况

(19840):整个堆大小

user=0.02

sys=0.00

real=0.02

[GC (CMS Initial Mark) [1 CMS-initial-mark: 8511K(13696K)] 9866K(19840K), 0.0040321 secs] [Times: user=0.01 sys=0.00, real=0.00 secs] 
   //8511 (13696) : 老年代使用(最大)
   //9866 (19840) : 整个堆使用(最大)
[CMS-concurrent-mark-start]
[CMS-concurrent-mark: 0.018/0.018 secs] [Times: user=0.01 sys=0.00, real=0.02 secs] 
   //这里的时间意义不大,因为是并发执行
[CMS-concurrent-preclean-start]
[CMS-concurrent-preclean: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
   //标记Card为Dirty,也称为Card Marking
[GC (CMS Final Remark) [YG occupancy: 1597 K (6144 K)][Rescan (parallel) , 0.0008396 secs][weak refs processing, 0.0000138 secs][class unloading, 0.0005404 secs][scrub symbol table, 0.0006169 secs][scrub string table, 0.0004903 secs][1 CMS-remark: 8511K(13696K)] 10108K(19840K), 0.0039567 secs] [Times: user=0.00 sys=0.00, real=0.00 secs] 
   //STW阶段,YG occupancy:年轻代占用及容量
   //[Rescan (parallel):STW下的存活对象标记
   //weak refs processing: 弱引用处理
   //class unloading: 卸载用不到的class
   //scrub symbol(string) table: 
      //cleaning up symbol and string tables which hold class-level metadata and 
      //internalized string respectively
   //CMS-remark: 8511K(13696K): 阶段过后的老年代占用及容量
   //10108K(19840K): 阶段过后的堆占用及容量

[CMS-concurrent-sweep-start]
[CMS-concurrent-sweep: 0.005/0.005 secs] [Times: user=0.00 sys=0.00, real=0.01 secs] 
   //标记已经完成,进行并发清理
[CMS-concurrent-reset-start]
[CMS-concurrent-reset: 0.000/0.000 secs] [Times: user=0.00 sys=0.00, real=0.00 secs]
   //重置内部结构,为下次GC做准备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26

# G1收集器

扩展阅读1 扩展阅读2 扩展阅读3

image-20201116150627930

Garbage First 垃圾收集器是目前垃圾收集器理论发展的最前沿成果,相比与CMS 收集器,G1 收集器两个最突出的改进是:

基于标记-整理算法,不产生内存碎片

可以非常精确控制停顿时间,在不牺牲吞吐量前提下,实现低停顿垃圾回收。

G1 收集器避免全区域垃圾收集,它把堆内存划分为大小固定的几个独立区域,并且跟踪这些区域的垃圾收集进度,同时在后台维护一个优先级列表,每次根据所允许的收集时间,优先回收垃圾最多的区域。

区域划分和优先级区域回收机制,确保G1 收集器可以在有限时间获得最高的垃圾收集效率。

# 相关技术

CardTable, CSet, RSet

# 特点

  • 并发收集
  • 压缩空闲空间, 不会延长 GC 的暂停时间
  • 更易预测的 GC 暂停时间
  • 适用不需要实现很高的吞吐量的场景

# G1日志详解

[GC pause (G1 Evacuation Pause) (young) (initial-mark), 0.0015790 secs]
//young -> 年轻代 Evacuation-> 复制存活对象 
//initial-mark 混合回收的阶段,这里是YGC混合老年代回收
   [Parallel Time: 1.5 ms, GC Workers: 1] //一个GC线程
      [GC Worker Start (ms):  92635.7]
      [Ext Root Scanning (ms):  1.1]
      [Update RS (ms):  0.0]
         [Processed Buffers:  1]
      [Scan RS (ms):  0.0]
      [Code Root Scanning (ms):  0.0]
      [Object Copy (ms):  0.1]
      [Termination (ms):  0.0]
         [Termination Attempts:  1]
      [GC Worker Other (ms):  0.0]
      [GC Worker Total (ms):  1.2]
      [GC Worker End (ms):  92636.9]
   [Code Root Fixup: 0.0 ms]
   [Code Root Purge: 0.0 ms]
   [Clear CT: 0.0 ms]
   [Other: 0.1 ms]
      [Choose CSet: 0.0 ms]
      [Ref Proc: 0.0 ms]
      [Ref Enq: 0.0 ms]
      [Redirty Cards: 0.0 ms]
      [Humongous Register: 0.0 ms]
      [Humongous Reclaim: 0.0 ms]
      [Free CSet: 0.0 ms]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)]
 [Times: user=0.00 sys=0.00, real=0.00 secs] 
//以下是混合回收其他阶段
[GC concurrent-root-region-scan-start]
[GC concurrent-root-region-scan-end, 0.0000078 secs]
[GC concurrent-mark-start]
//无法evacuation,进行FGC
[Full GC (Allocation Failure)  18M->18M(20M), 0.0719656 secs]
   [Eden: 0.0B(1024.0K)->0.0B(1024.0K) Survivors: 0.0B->0.0B Heap: 18.8M(20.0M)->18.8M(20.0M)], [Metaspace: 38
76K->3876K(1056768K)] [Times: user=0.07 sys=0.00, real=0.07 secs]

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38

# 常用参数

# GC常用参数

  • -Xmn -Xms -Xmx -Xss 年轻代 最小堆 最大堆 栈空间

  • -XX:+UseTLAB 使用TLAB,默认打开

  • -XX:+PrintTLAB 打印TLAB的使用情况

  • -XX:TLABSize 设置TLAB大小

  • -XX:+DisableExplictGC System.gc()不管用 ,FGC

  • -XX:+PrintGC

  • -XX:+PrintGCDetails

  • -XX:+PrintHeapAtGC

  • -XX:+PrintGCTimeStamps

  • -XX:+PrintGCApplicationConcurrentTime (低) 打印应用程序时间

  • -XX:+PrintGCApplicationStoppedTime (低) 打印暂停时长

  • -XX:+PrintReferenceGC (重要性低) 记录回收了多少种不同引用类型的引用

  • -verbose:class 类加载详细过程

  • -XX:+PrintVMOptions

  • -XX:+PrintFlagsFinal -XX:+PrintFlagsInitial(初始化默认值) 必须会用

    java -XX:+PrintFlagsFinal -version | grep [G1]

  • -Xloggc:opt/log/gc.log

  • -XX:MaxTenuringThreshold 升代年龄,最大值15

  • 锁自旋次数 -XX:PreBlockSpin 热点代码检测参数-XX:CompileThreshold 逃逸分析 标量替换 ... 这些不建议设置

# Parallel常用参数

  • -XX:SurvivorRatio
  • -XX:PreTenureSizeThreshold 大对象到底多大
  • -XX:MaxTenuringThreshold
  • -XX:+ParallelGCThreads 并行收集器的线程数,同样适用于CMS,一般设为和CPU核数相同
  • -XX:+UseAdaptiveSizePolicy 自动选择各区大小比例

# CMS常用参数

  • -XX:+UseConcMarkSweepGC
  • -XX:ParallelCMSThreads CMS线程数量
  • -XX:CMSInitiatingOccupancyFraction 使用多少比例的老年代后开始CMS收集,默认是68%(近似值),如果频繁发生SerialOld卡顿,应该调小,(频繁CMS回收)
  • -XX:+UseCMSCompactAtFullCollection 在FGC时进行压缩
  • -XX:CMSFullGCsBeforeCompaction 多少次FGC之后进行压缩
  • -XX:+CMSClassUnloadingEnabled
  • -XX:CMSInitiatingPermOccupancyFraction 达到什么比例时进行Perm回收
  • GCTimeRatio 设置GC时间占用程序运行时间的百分比
  • -XX:MaxGCPauseMillis 停顿时间,是一个建议时间,GC会尝试用各种手段达到这个时间,比如减小年轻代

# G1常用参数

  • -XX:+UseG1GC
  • -XX:MaxGCPauseMillis 建议值,G1会尝试调整Young区的块数来达到这个值
  • -XX:GCPauseIntervalMillis ?GC的间隔时间
  • -XX:+G1HeapRegionSize 分区大小,建议逐渐增大该值,1 2 4 8 16 32。 随着size增加,垃圾的存活时间更长,GC间隔更长,但每次GC的时间也会更长 ZGC做了改进(动态区块大小)
  • G1NewSizePercent 新生代最小比例,默认为5%
  • G1MaxNewSizePercent 新生代最大比例,默认为60%
  • GCTimeRatio GC时间建议比例,G1会根据这个值调整堆空间
  • ConcGCThreads 线程数量
  • InitiatingHeapOccupancyPercent 启动G1的堆空间占用比例

# 其他扩展

# CardTable

card_table

# CSet

CSet 就是 Collection Set 的缩写

是一组可以被回收的分区的集合.

# RSet

RSet(Remembered Set)他记录了其他 Region 中的对象到本 Region 的引用,

RSet 的价值在于使得垃圾收集器不需要扫描整个堆就可以找到谁引用了当前分区中的对象, 只需要扫描 RSet 即可.

# 三色标记

  • 白色: 未被标记的对象
  • 灰色: 自己已标记完成, 其成员变量还没有标记
  • 黑色: 自身和成员变量都被标记完成

# 漏标

漏标问题

image-20201116160604817

解决方式

image-20201116160836953

# 颜色指针

# 作业

  1. -XX:MaxTenuringThreshold控制的是什么? A: 对象升入老年代的年龄 B: 老年代触发FGC时的内存垃圾比例

  2. 生产环境中,倾向于将最大堆内存和最小堆内存设置为:(为什么?) A: 相同 B:不同

  3. JDK1.8默认的垃圾回收器是: A: ParNew + CMS B: G1 C: PS + ParallelOld D: 以上都不是

  4. 什么是响应时间优先?

  5. 什么是吞吐量优先?

  6. ParNew和PS的区别是什么?

  7. ParNew和ParallelOld的区别是什么?(年代不同,算法不同)

  8. 长时间计算的场景应该选择:A:停顿时间 B: 吞吐量

  9. 大规模电商网站应该选择:A:停顿时间 B: 吞吐量

  10. HotSpot的垃圾收集器最常用有哪些?

  11. 常见的HotSpot垃圾收集器组合有哪些?

  12. JDK1.7 1.8 1.9的默认垃圾回收器是什么?如何查看?

  13. 所谓调优,到底是在调什么?

  14. 如果采用PS + ParrallelOld组合,怎么做才能让系统基本不产生FGC

  15. 如果采用ParNew + CMS组合,怎样做才能够让系统基本不产生FGC

    1. 加大JVM内存

    2. 加大Young的比例

    3. 提高Y-O的年龄

    4. 提高S区比例

    5. 避免代码内存泄漏

  16. G1是否分代?G1垃圾回收器会产生FGC吗?

  17. 如果G1产生FGC,你应该做什么?

    1. 扩内存
    2. 提高CPU性能(回收的快,业务逻辑产生对象的速度固定,垃圾回收越快,内存空间越大)
    3. 降低MixedGC触发的阈值,让MixedGC提早发生(默认是45%)
  18. 问:生产环境中能够随随便便的dump吗? 小堆影响不大,大堆会有服务暂停或卡顿(加live可以缓解),dump前会有FGC

  19. 问:常见的OOM问题有哪些? 栈 堆 MethodArea 直接内存

# 参考资料

  1. https://blogs.oracle.com/jonthecollector/our-collectors
  2. https://docs.oracle.com/javase/8/docs/technotes/tools/unix/java.html
  3. http://java.sun.com/javase/technologies/hotspot/vmoptions.jsp
  4. JVM调优参考文档:https://docs.oracle.com/en/java/javase/13/gctuning/introduction-garbage-collection-tuning.html#GUID-8A443184-7E07-4B71-9777-4F12947C8184
  5. https://www.cnblogs.com/nxlhero/p/11660854.html 在线排查工具
  6. https://www.jianshu.com/p/507f7e0cc3a3 arthas常用命令
  7. Arthas手册:
    1. 启动arthas java -jar arthas-boot.jar
    2. 绑定java进程
    3. dashboard命令观察系统整体情况
    4. help 查看帮助
    5. help xx 查看具体命令帮助
  8. jmap命令参考: https://www.jianshu.com/p/507f7e0cc3a3
    1. jmap -heap pid
    2. jmap -histo pid
    3. jmap -clstats pid
上次更新时间: 2020/11/17 上午12:35:40