文章目录
- 1_介绍
- 2_原理
- 1.0版本
- 2.0版本
- 3_ShenandoahGC的执行流程
- 4_并发转移阶段 – 并发问题
1_介绍
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日志
/*
* Copyright (c) 2005, 2014, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package org.sample;
import com.sun.management.OperatingSystemMXBean;
import org.openjdk.jmh.annotations.*;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryUsage;
import java.util.ArrayList;
import java.util.List;
import java.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)
public class MyBenchmark {
//每次测试对象大小 4KB和4MB
@Param({"4","4096"})
int perSize;
private void test(Blackhole blackhole){
//每次循环创建堆内存60%对象 JMX获取到Java运行中的实时数据
MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
//获取堆内存大小
MemoryUsage heapMemoryUsage = memoryMXBean.getHeapMemoryUsage();
//获取到剩余的堆内存大小
long heapSize = (long) ((heapMemoryUsage.getMax() - heapMemoryUsage.getUsed()) * 0.6);
//计算循环次数
long size = heapSize / (1024 * perSize);
for (int i = 0; i < 4; i++) {
List<byte[]> objects = new ArrayList<>((int)size);
for (int j = 0; j < size; j++) {
objects.add(new byte[1024 * perSize]);
}
blackhole.consume(objects);
}
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseSerialGC"})
public void serialGC(Blackhole blackhole){
test(blackhole);
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseParallelGC"})
public void parallelGC(Blackhole blackhole){
test(blackhole);
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g"})
public void g1(Blackhole blackhole){
test(blackhole);
}
@Benchmark
@Fork(value = 1,jvmArgsAppend = {"-Xms4g","-Xmx4g","-XX:+UseShenandoahGC"})
public void shenandoahGC(Blackhole blackhole){
test(blackhole);
}
public static void main(String[] args) throws RunnerException {
Options opt = new OptionsBuilder()
.include(MyBenchmark.class.getSimpleName())
.forks(1)
.build();
new Runner(opt).run();
}
}
测试结果:
Shenandoah GC对小对象的GC停顿很短,但是大对象效果不佳。
更多调优策略详见:https://wiki.openjdk.org/display/shenandoah/Main。
2_原理
1.0版本
如果转移阶段未完成,此时转移前的对象和转移后的对象都会存活。如果用户去访问数据,需要使用转移后的数据。 ShenandoahGC使用了读前屏障,根据对象的前向指针来获取到转移后的对象并读取。
写入数据时会使用写前屏障,判断Mark Word中的GC状态,如果GC状态为0证明没有处于GC过程中,直接写入,如果不为0则根据GC状态值确认当前处于垃圾回收的哪个阶段,让用户线程执行垃圾回收相关的任务。
1.0版本的缺点:
1、对象内存大大增加,每个对象都需要增加8个字节的前向指针,基本上会占用5% - 10%的空间。
2、读屏障中加入了复杂的指令,影响使用效率。
2.0版本
2.0版本优化了前向指针的位置,仅转移阶段将其放入了Mark Word中。
3_ShenandoahGC的执行流程
4_并发转移阶段 – 并发问题
如果用户线程在帮忙转移时,ShenandoahGC线程也发现这个对象需要复制,那么就会去尝试写入前向指针,使用了类似CAS的方式来实现,只有一个线程能成功修改,其他线程会放弃转移的操作。