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 启动程序进行测试
https://shorturl.fm/CnMWL
https://shorturl.fm/fpBiq
https://shorturl.fm/YW0YX
https://shorturl.fm/k626T
https://shorturl.fm/tE7p6
https://shorturl.fm/QdaBd
https://shorturl.fm/x8PzV
https://shorturl.fm/tbR8v
https://shorturl.fm/N5COx
Unlock top-tier commissions—become our affiliate partner now!
Share our products and watch your earnings grow—join our affiliate program!
Apply now and receive dedicated support for affiliates!
Get started instantly—earn on every referral you make!
Boost your income effortlessly—join our affiliate network now!