12新一代的GC

不同的垃圾回收器设计的目标是不同的,如下图所示:

1.Shenandoah GC

Shenandoah 是由Red Hat开发的一款低延迟的垃圾收集器,Shenandoah 并发执行大部分 GC 工作,包括并发的整理,堆大小对STW的时间基本没有影响。

1、下载。Shenandoah只包含在OpenJDK中,默认不包含在内需要单独构建,可以直接下载构建好的。 下载地址:https://builds.shipilev.net/openjdk-jdk-shenandoah/ 选择方式如下: {aarch64, arm32-hflt, mipsel, mips64el, ppc64le, s390x, x86_32, x86_64}:架构,使用arch命令选择对应的的架构。 {server,zero}:虚拟机类型,选择server,包含所有GC的功能。 {release, fastdebug, Slowdebug, optimization}:不同的优化级别,选择release,性能最高。 {gcc*-glibc*, msvc*}:编译器的版本,选择较高的版本性能好一些,如果兼容性有问题(无法启动),选择较低的版本。

2、配置。将OpenJDK配置到环境变量中,使用java –version进行测试。打印出如下内容代表成功。

3、添加参数,运行Java程序。

-XX:+UseShenandoahGC 开启Shenandoah GC

-Xlog:gc 打印GC日志

packageorg.sample;
importcom.sun.management.OperatingSystemMXBean;
importorg.openjdk.jmh.annotations.*;
importorg.openjdk.jmh.infra.Blackhole;
importorg.openjdk.jmh.runner.Runner;
importorg.openjdk.jmh.runner.RunnerException;
importorg.openjdk.jmh.runner.options.Options;
importorg.openjdk.jmh.runner.options.OptionsBuilder;

importjava.lang.management.ManagementFactory;
importjava.lang.management.MemoryMXBean;
importjava.lang.management.MemoryUsage;
importjava.util.ArrayList;
importjava.util.List;
importjava.util.concurrent.TimeUnit;

//执行5轮预热,每次持续2秒
@Warmup(iterations = 5, time = 2, timeUnit = TimeUnit.SECONDS)
//输出毫秒单位
@OutputTimeUnit(TimeUnit.MILLISECONDS)
//统计方法执行的平均耗时
@BenchmarkMode(Mode.AverageTime)
//java -jar benchmarks.jar -rf json
@State(Scope.Benchmark)
publicclassMyBenchmark {

    //每次测试对象大小 4KB和4MB@Param({"4","4096"})
    intperSize;

    privatevoidtest(Blackhole blackhole){

        //每次循环创建堆内存60%对象 JMX获取到Java运行中的实时数据MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
        //获取堆内存大小MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
        //获取到剩余的堆内存大小longheapSize = (long) ((heapMemoryUsage.getMax() - heapMemoryUsage.getUsed()) * 0.6);
        //计算循环次数longsize = heapSize / (1024 * perSize);

        for(inti = 0; i < 4; i++) {
            List<byte[]> objects = newArrayList<>((int)size);
            for(intj = 0; j < size; j++) {
                objects.add(newbyte[1024 * perSize]);
            }
            blackhole.consume(objects);
        }
    }

    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseSerialGC"})
    publicvoidserialGC(Blackhole blackhole){
        test(blackhole);
    }
    
    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseParallelGC"})
    publicvoidparallelGC(Blackhole blackhole){
        test(blackhole);
    }
    
    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g"})
    publicvoidg1(Blackhole blackhole){
        test(blackhole);
    }

    @Benchmark
    @Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseShenandoahGC"})
    publicvoidshenandoahGC(Blackhole blackhole){
        test(blackhole);
    }


    publicstaticvoidmain(String[] args) throwsRunnerException {

        Options opt = newOptionsBuilder()
                .include(MyBenchmark.class.getSimpleName())
                .forks(1)
                .build();

        newRunner(opt).run();
    }
}

测试结果:

Shenandoah GC对小对象的GC停顿很短,但是大对象效果不佳。

2.ZGC

ZGC 是一种可扩展的低延迟垃圾回收器。ZGC 在垃圾回收过程中,STW的时间不会超过一毫秒,适合需要低延迟的应用。支持几百兆到16TB 的堆大小,堆大小对STW的时间基本没有影响。

ZGC降低了停顿时间,能降低接口的最大耗时,提升用户体验。但是吞吐量不佳,所以如果Java服务比较关注QPS(每秒的查询次数)那么G1是比较不错的选择。

OracleJDK和OpenJDK中都支持ZGC,阿里的DragonWell龙井JDK也支持ZGC但属于其自行对OpenJDK 11的ZGC进行优化的版本。

建议使用JDK17之后的版本,延迟较低同时无需手动配置并行线程数。

分代 ZGC添加如下参数启用 -XX:+UseZGC -XX:+ZGenerational

非分代 ZGC通过命令行选项启用 -XX:+UseZGC

ZGC在设计上做到了自适应,根据运行情况自动调整参数,让用户手动配置的参数最少化。

  • 自动设置年轻代大小,无需设置-Xmn参数。

自动晋升阈值(复制中存活多少次才搬运到老年代),无需设置-XX:TenuringThreshold。

JDK17之后支持自动的并行线程数,无需设置-XX:ConcGCThreads。

  • 需要设置的参数:  -Xmx 值 最大堆内存大小。这是ZGC最重要的一个参数,必须设置。ZGC在运行过程中会使用一部分内存用来处理垃圾回收,所以尽量保证堆中有足够的空间。设置多少值取决于对象分配的速度,根据测试情况来决定。
  • 可以设置的参数:  -XX:SoftMaxHeapSize=值。ZGC会尽量保证堆内存小于该值,这样在内存靠近这个值时会尽早地进行垃圾回收,但是仍有可能会超过该值。例如,-Xmx5g -XX:SoftMaxHeapSize=4g 这个参数设置,ZGC会尽量保证堆内存小于4GB,最多不会超过5GB。
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseZGC","-XX:+UseLargePages"})
publicvoidzGC(Blackhole blackhole){
    test(blackhole);
}

@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseZGC","-XX:+ZGenerational","-XX:+UseLargePages"})
publicvoidzGCGenerational(Blackhole blackhole){
    test(blackhole);
}

ZGC整体表现还是非常不错的,分代也让ZGC的停顿时间有更好的表现。

ZGC 中可以使用Linux的Huge Page大页技术优化性能,提升吞吐量、降低延迟。

注意:安装过程需要 root 权限,所以ZGC默认没有开启此功能。

操作步骤:

1、计算所需页数,Linux x86架构中大页大小为2MB,根据所需堆内存的大小估算大页数量。比如堆空间需要16G,预留2G(JVM需要额外的一些非堆空间),那么页数就是18G / 2MB = 9216。

2、配置系统的大页池以具有所需的页数(需要root权限):

$ echo 9216 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages

3、添加参数-XX:+UseLargePages 启动程序进行测试

14 条评论

  1. Lee8 2025-11-28 19:21 回复
  2. Crystal4716 2025-11-30 19:31 回复
  3. Regina2967 2025-12-02 10:22 回复
  4. Fallon4341 2025-12-12 02:33 回复
  5. Regina4728 2025-12-17 15:20 回复
  6. Ann832 2025-12-17 19:53 回复
  7. Miguel2674 2025-12-20 06:20 回复
  8. Zoey3962 2025-12-22 06:19 回复
  9. Elise2732 2026-01-03 16:20 回复

发表评论

您的邮箱地址不会被公开。 必填项已用 * 标注