内存与垃圾回收篇--17 垃圾回收器 -B站尚硅谷JVM课程学习

本文详细介绍了Java的各种垃圾回收器,包括Serial、ParNew、Parallel Scavenge、CMS和G1,重点讨论了它们的性能指标、工作模式以及如何选择合适的垃圾回收器。G1以其低延迟和区域化分代式的特点,成为大内存应用的首选。文章还提到了GC日志分析的重要性以及垃圾回收的新发展,如Shenandoah和ZGC。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

GC分类与性能指标

分类:

  • 按线程数分:串行垃圾回收器(硬件配置低,单CPU;Client模式下)和并行垃圾回收器,都会STW
  • 按工作模式分:并发式垃圾回收器(交替工作)和独占式垃圾回收器
  • 按碎片处理方式:压缩式垃圾回收器和非压缩式垃圾回收器(指针碰撞、维护空闲列表)
  • 按工作的内存区间分:年轻代垃圾回收器和老年代垃圾回收器

性能指标:

  • 吞吐量(throughput):运行用户代码的时间占总时间(程序运行时间+内存回收)的比例,越大越好
  • 垃圾收集开销:吞吐量的补数
  • 暂停时间(pause time):STW时间,越小越好
  • 收集频率:收集操作发生的频率
  • 内存占用:java堆区所占的内存大小
  • 快速:一个对象从诞生到被回收所经历的时间

在这里插入图片描述
高吞吐量较好是因为让用户感觉到只有用户线程在工作
低延迟较好是因为让用户感觉到自己线程不被打断
目标:在最大吞吐量优先的情况下,降低停顿时间

不同的垃圾回收器概述

  • 1999 Serial GC,ParNew Serial的多线程版本
  • 2002 Parallel GC,Concurrent Mark Sweep GC(CMS)
  • 2012 G1
  • 2017 G1为默认垃圾收集器
  • 2018 G1垃圾回收器的并行完整垃圾回收
  • 2018.9 Epsilon 又称为No-op,ZGC(可伸缩的低延迟垃圾回收器)
  • 2019.3 增加G1
  • 2019.9 增强ZGC
  • 2020.3 jdk14中删除CMS

7种经典垃圾回收器:

  • 串行回收器:Serial,Serial Old
  • 并行回收器:Parallel,Parallel Scavenge,Parallel Old
  • 并发回收器:G1,CMS

组合关系:
在这里插入图片描述

-XX:+PrintCommandLineFlags

package chapter17;

import java.util.ArrayList;

public class PrintGCTest {
    public static void main(String[] args) {
        ArrayList<Object> list = new ArrayList<>();

        while (true){
            byte[] bytes = new byte[100];
            list.add(bytes);
            try {
                Thread.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 
hello world 

Serial回收器:串行回收

Serial 采用复制算法串行回收和 “Stop the World”机制的方式执行内存回收
Serial Old 采用标记-压缩算法串行回收和 “Stop the World”机制的方式执行内存回收。Client模式下老年代的垃圾回收器;在Server 模式下 1)与新生代的Parallel Scavenge配合使用,2)作为老年代CMS收集器的后背垃圾收集方案。
简单高效,单核CPU会用

-XX:+UserSerialGC :新生代使用Serial GC,老年代使用Serial Old GC

-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseSerialGC 

parNew回收器:并行回收

在这里插入图片描述
并行回收执行内存回收,STW机制,复制算法
在Server模式下新生代的默认垃圾回收器
ParNew运行在多CPU环境下,可以充分利用CPU,可以快速完成垃圾收集。但在单CPU环境下Serial效率更高,不需要来回切换CPU

-XX:+PrintCommandLineFlags -XX:+UseParNewGC表示新生代使用ParNew GC
-XX:ParallelGCThreads设置线程数

-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParNewGC 

Parallel Scavenge 回收器:吞吐量优先

在这里插入图片描述
Parallel采用 复制算法并行回收STW机制
和ParNew不同,Parallel目标是达到一个可控制的吞吐量,还有自适应调节策略
高吞吐量可以高效率的利用CPU时间,适合后台运算而不需要太多交互的任务,如批量处理,订单处理,Parallel Old 用于处理老年代来替代Serial Old,采用 标记-压缩并行回收STW机制

-XX:+PrintCommandLineFlags
-XX:+UseParallelGC 新生代垃圾回收器
-XX:+UseParallelOldGC 老年代垃圾回收器
-XX:ParallelGCThreads 设置年轻代并行收集器的线程数,CPU个数小于8为8
-XX:MaxGCPauseMills 设置最大停顿时间,谨慎使用
-XX:GCTimeRatio
-XX:+UseAdaptiveSizePolicy 设置Parallel收集器的自适应调节策略,默认开启

-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544 -XX:+PrintCommandLineFlags -XX:+UseCompressedClassPointers -XX:+UseCompressedOops -XX:-UseLargePagesIndividualAllocation -XX:+UseParallelGC 

CMS回收器:低延迟

Concurrent Mark Sweep 并发收集器,并发标记-清除算法STW
在这里插入图片描述

  • 初始标记:标记出GC Roots能直接关联到的对象,STW,时间较短
  • 并发标记:关联到的直接对象开始遍历整个对象图的过程,并发
  • 重新标记:修正并发标记期间,因用户程序继续运行导致标记产生变动的那一部分对象的标记记录,STW,时间较短
  • 并发清除:清理删除掉标记阶段判断的已经死亡的对象,释放内存空间,并发

整体的回收是低延迟的
确保应用程序用户线程有足够的内存可用
当堆内存使用率达到某一阈值时,便开始运行回收,出现了Concurrent Mode Failure,临时启用Serial Old
标记清除算法,会产生内存碎片,选择空闲列表对对象分配内存空间

为什么不适用标记-压缩算法呢?
因为要保证用户线程正常执行,所以不能改变程序的地址

弊端:

  • 会产生内存碎片,无法分配大对象,会触发Full GC
  • 对CPU资源敏感
  • 无法处理浮动垃圾:并发标记阶段如果产生新的垃圾对象,CMS将无法对这些垃圾对象进行标记,最终导致新产生的垃圾对象没有被及时回收

-XX:+UseConcurrentMarkSweepGC 老年代使用CMS回收器
-XX:CMSlnitiatingOccupanyFraction 堆内存使用率的阈值,一旦到达阈值,开始回收。内存增长缓慢,阈值可以大一点
-XX:+UseCMSComactAtFullCollection:指定执行完Full GC后堆内存空间进行压缩整理
-XX:CMSFullGCsBeforeCompation 设置在执行多少次Full GC后对内存空间进行压缩整理
-XX:ParallelCMSThreads 设置CMS线程数量

-XX:InitialHeapSize=132742784 -XX:MaxHeapSize=2123884544
-XX:MaxNewSize=348966912 -XX:MaxTenuringThreshold=6 
-XX:OldPLABSize=16 -XX:+PrintCommandLineFlags 
-XX:+UseCompressedClassPointers -XX:+UseCompressedOops 
-XX:+UseConcMarkSweepGC -XX:-UseLargePagesIndividualAllocation 
-XX:+UseParNewGC 

G1回收器:区域化分代式

将堆内存分割成很多不相关的区域(Region),避免在整个堆中进行全域的垃圾收集,优先回收垃圾最大量的区域。
多核CPU和大容量内存, jdk9后默认

优点:

1.并行和并发

  • 并行性:可以有多个GC线程同时工作,有效利用多核运算能力,STW
  • 并发性:可以与应用程序交替执行

2.分代收集

  • 分代型垃圾回收器,多个region中有多个新生代多个老年代,不需要连续,不需要固定大小
  • 将堆空间分成若干个区域,包含逻辑上的年轻代和老年代
  • 同时兼顾年轻代和老年代

3.空间整合

  • CMS 标记-清除、内存碎片,若干次GC后整理碎片
  • Region之间是复制算法,不存在碎片,整体上是标记-压缩,有利于程序长时间运行

4.可预测的停顿时间模型(软实时 soft real-time)

  • 分区可以只选取部分区域进行内存回收,不需要全局停顿
  • 跟踪Region的价值(空间大小和所需时间价值),优先回收价值大的region,保证在有限时间内可以获得尽可能高的收集效率

缺点:
小内存应用上CMS的表现大概率优于G1,而G1在大内存应用上发挥优势

参数设置:
-XX:+UseG1GC
-XX:G1HeapRegionSize 设置每个region的大小,是2的幂,范围是1MB-32MB
-XX:MaxGCPauseMills 最大GC停顿时间指标,默认200ms
-XX:ParallelGCThread STW工作线程数
-XX:ConcGCThreads 并发标记的线程数,占ParallelGCThread 的1/4
-XX:InitiatingHeapOccupancyPercent 触发并发GC周期的堆内存阈值

G1回收器的常见操作步骤

  • 第一步:开启G1垃圾回收器
  • 第二步:设置堆的最大内存
  • 第三步:设置最大停顿时间

G1使用场景

  • 服务器端,大内存多处理器
  • 低延迟并堆空间较大
  • 替换原本CMS
  • 采用应用线程承担后台 的GC工作

分区region:化整为零
Eden
survivor
old
humongous:矩形对象,用来存储大对象,如果一个H区存不下,那么用连续的H区来存储
指针碰撞
TLAB:线程一小份

G1垃圾回收过程:

  • 年轻代GC:当年轻代的Eden区用尽时开始年轻代回收
  • 当堆内存使用45%,老年代并发标记
  • 混合回收

记忆集 Remembered Set
问题:一个region对象可能被其他region对象引用,判断对象是否存活时,是否需要扫描整个Java堆才能保证准确?回收新生代不得不要扫描整个老年代?

解决:给每个region附带一个Remembered Set记录谁引用了当前region,每次引用类型数据写操作时,都会产生一个写屏障(Write Barrier)暂时中断操作,将引用写入到RSet中。

在这里插入图片描述

G1回收过程一:年轻代GC

  • 扫描根。根引用连同RSet记录的外部引用作为扫描存活对象的入口
  • 更新Rset。RSet可以准确反映老年代对所在的内存分段中对象的引用
  • 处理RSet
  • 复制对象
  • 处理引用。处理软弱虚引用

G1回收过程二:并发标记

  • 初始标记。STW
  • 根区域扫描。扫描survivor区直接可达的老年代区域对象,要在YGC前完成
  • 并发标记。若发现区域中所有对象都是垃圾,则立即回收
  • 再次标记。STW,使用初始快照算法snapshot-at-the-beginning(SATB)
  • 独占清理。STW
  • 并发清理

G1回收过程三:混合回收

  • 并发标记结束后,老年代中百分百垃圾的内存被回收,部分为垃圾的内存分段计算出来。默认老年代的内存分段会分8次
  • 混合回收的回收集(Collection Set)包括八分之一的老年代内存分段,Eden区内存分段,Survivor内存分段。
  • 由于老年代中的内存分段默认为8次回收,G1会优先回收垃圾多的内存分段。垃圾站内存分段比例越高,越会被先回收
    在这里插入图片描述

G1回收过程四:Full GC

导致 G1 Full GC原因:

  • 没有足够多的to-space存放晋升的对象
  • 并发处理过程完成之前空间耗尽

补充:回收阶段设计成与用户程序一起并发执行

G1 垃圾回收的优化建议
1.年轻代大小:
避免使用-Xmn等相关参数设置年轻代大小
固定年轻代的大小会覆盖暂停时间目标
2.暂停时间目标不要太过严苛
G1 GC的吞吐量目标是90%的应用程序时间和10%的垃圾回收时间
评估 G1 GC的吞吐量时。暂停时间目标不要太严苛,会影响到吞吐量

垃圾回收器总结

在这里插入图片描述

怎么选择垃圾回收器?

  • 优先调整堆的大小让JVM自适应完成
  • 内存小于100M,选择串行
  • 单核、单机程序,没有停顿时间要求,串行
  • 多CPU、高吞吐量,允许停顿时间超过1s,选择并行或者JVM自己选择
  • 多CPU、低停顿、快速响应,并发收集器G1(大多数互联网是G1)

GC日志分析

-XX:+PrintGC 输出日志信息

[GC (Allocation Failure)  42795K->40696K(95232K), 0.0567535 secs]
[Full GC (Ergonomics)  40696K->40652K(135680K), 0.0164750 secs]
[GC (Allocation Failure)  81564K->80652K(136192K), 0.0503108 secs]
[Full GC (Ergonomics)  80652K->80648K(176640K), 0.0236996 secs]

其中 GC FullGC表示GC的类型,GC只在新生代上进行,Full GC表示永生代、新生代、老年代
Allocation Failure:GC发生原因
42795K->40696K:堆在GC前的大小和后的大小
95232K:现在堆大小
0.0567535 secs:GC持续时间

-XX:+PrintGCDetails 输出日志详细信息

[GC (Allocation Failure) [PSYoungGen: 42795K->696K(53760K)] 42795K->40704K(95232K), 0.0610055 secs] [Times: user=0.01 sys=0.03, real=0.06 secs] 
[Full GC (Ergonomics) [PSYoungGen: 696K->0K(53760K)] [ParOldGen: 40008K->40652K(81920K)] 40704K->40652K(135680K), [Metaspace: 3440K->3440K(1056768K)], 0.0849122 secs] [Times: user=0.05 sys=0.00, real=0.09 secs] 
[GC (Allocation Failure) [PSYoungGen: 40911K->0K(54272K)] 81564K->80652K(136192K), 0.0633675 secs] [Times: user=0.06 sys=0.06, real=0.06 secs] 
[Full GC (Ergonomics) [PSYoungGen: 0K->0K(54272K)] [ParOldGen: 80652K->80648K(122880K)] 80652K->80648K(177152K), [Metaspace: 3440K->3440K(1056768K)], 0.0273931 secs] [Times: user=0.06 sys=0.00, real=0.03 secs] 

PSYoungGen 696K->0K:使用parallel Scavenge 并行垃圾回收器的新生代GC前后的大小
ParOldGen: 40008K->40652K:使用parallel Old 并行垃圾回收器的老年代GC前后的大小
Metaspace: 3440K->3440K : 元数据区GC前后大小的变化
[Times: user=0.06 垃圾收集器花费的所有CPU时间 sys=0.00 花费在等待系统调用或系统事件的时间, real=0.03 secs GC从开始到结束的时间,包括其他进行占用时间片的实际时间]

-XX:+PrintGCTimeStamps 输出GC时间戳
-XX:+PrintGCDateStamps 输出GC时间戳
-Xloggc:…/logs/gc.log 日志文件输出路径,可通过easyGC 查看

在这里插入图片描述
在这里插入图片描述

package chapter17;

public class GCLogTest {
    private  static final int _1MB = 1024 * 1024;
    public static void testAllocation(){
        byte[] allocation1,allocation2,allocation3, allocation4;
        allocation1 = new byte[2 * _1MB];
        allocation2 = new byte[2 * _1MB];
        allocation3 = new byte[2 * _1MB];
        allocation4 = new byte[4 * _1MB];
    }
    public static void main(String[] args) {
        testAllocation();
    }
}

参数:-Xms20m -Xmx20m -Xmn10m -XX:SurvivorRatio=8 -XX:+UseSerialGC -XX:+PrintGCDetails

[GC (Allocation Failure) [DefNew: 8190K->628K(9216K), 0.0093693 secs] 8190K->6772K(19456K), 0.0094574 secs] [Times: user=0.00 sys=0.02, real=0.01 secs] 
Heap
 def new generation   total 9216K(8196+1024, used 4806K [0x00000000fec00000, 0x00000000ff600000, 0x00000000ff600000)
  eden space 8192K,  51% used [0x00000000fec00000, 0x00000000ff014930, 0x00000000ff400000)
  from space 1024K,  61% used [0x00000000ff500000, 0x00000000ff59d150, 0x00000000ff600000)
  to   space 1024K,   0% used [0x00000000ff400000, 0x00000000ff400000, 0x00000000ff500000)
 tenured generation   total 10240K, used 6144K [0x00000000ff600000, 0x0000000100000000, 0x0000000100000000)
   the space 10240K,  60% used [0x00000000ff600000, 0x00000000ffc00030, 0x00000000ffc00200, 0x0000000100000000)
 Metaspace       used 3426K, capacity 4496K, committed 4864K, reserved 1056768K
  class space    used 372K, capacity 388K, committed 512K, reserved 1048576K

Process finished with exit code 0

垃圾回收器的新发展

Shenandoah GC 红帽 降低低停顿,吞吐量高

令人震惊的ZGC

  • 尽可能对吞吐量影响不大的情况下,将垃圾回收停顿的时间控制在10ms以内
  • 基于region,并发的标记-压缩算法,以低延迟为首要目标
  • 并发标记-并发预备重分配-并发重分配-并发映射
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值