Java 使用JMH和Mode.AverageTime的OutOfMemory

Java 使用JMH和Mode.AverageTime的OutOfMemory,java,stringbuilder,jmh,Java,Stringbuilder,Jmh,我正在编写一个微型基准测试来比较使用+运算符和StringBuilder的字符串连接。为此,我基于以下内容创建了一个JMH基准类: 当我运行基准测试时,我得到stringBuilderConcatenation方法的以下错误: java.lang.OutOfMemoryError: Java heap space at java.util.Arrays.copyOf(Arrays.java:3332) at java.lang.AbstractStringBuilder.expa

我正在编写一个微型基准测试来比较使用+运算符和StringBuilder的字符串连接。为此,我基于以下内容创建了一个JMH基准类:

当我运行基准测试时,我得到
stringBuilderConcatenation
方法的以下错误:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.expandCapacity(AbstractStringBuilder.java:137)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:121)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:421)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
    at link.pellegrino.string_concatenation.StringConcatenationBenchmark.stringBuilderConcatenation(StringConcatenationBenchmark.java:29)
    at link.pellegrino.string_concatenation.generated.StringConcatenationBenchmark_stringBuilderConcatenation.stringBuilderConcatenation_avgt_jmhStub(StringConcatenationBenchmark_stringBuilderConcatenation.java:165)
    at link.pellegrino.string_concatenation.generated.StringConcatenationBenchmark_stringBuilderConcatenation.stringBuilderConcatenation_AverageTime(StringConcatenationBenchmark_stringBuilderConcatenation.java:130)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:497)
    at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:430)
    at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:412)
    at java.util.concurrent.FutureTask.run(FutureTask.java:266)
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
    at java.lang.Thread.run(Thread.java:745)
我认为默认的JVM堆大小必须增加,所以我尝试使用JMH提供的
-Xmx10G
值和
-jvmArgs
选项来允许最大10GB。不幸的是,我仍然得到了错误

因此,我试图将
batchSize
参数的值减少到1,但仍然得到一个OutOfMemoryError

我找到的唯一解决方法是将基准模式设置为
模式。SingleShotTime
。由于这种模式似乎考虑了一个批处理(即使S/OP显示在单元列中),所以我得到了我想要的度量:执行批处理操作的平均时间。但是,我仍然不明白为什么它不能使用
模式.AverageTime

还请注意,无论使用何种基准模式,方法
stringConcatenation
的基准都能按预期工作。只有使用StringBuilder的
stringBuilderConcatenation
方法才会出现此问题

任何帮助都可以理解为什么上一个示例不使用设置为
模式的基准模式。欢迎使用AverageTime


我使用的JMH版本是1.10.4。

你说得对,
模式。SingleShotTime是你需要的:它测量单个批次的时间。使用
模式.AverageTime
时,迭代仍然有效,直到迭代时间结束(默认情况下为1秒)。它测量每个执行单个批次的时间(仅统计在执行时间内完全完成的批次),因此最终结果不同,但执行时间相同

另一个问题是
@Setup(Level.Iteration)
强制在每次迭代之前执行设置,但不是在每次批处理之前。因此,字符串实际上不受批处理大小的限制。字符串版本不会导致
OutOfMemoryError
,因为它比
StringBuilder
慢得多,所以在1秒内它能够生成更短的字符串

修复基准测试(同时仍使用平均时间模式和batchSize参数)的不太好的方法是手动重置string/stringBuilder:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(batchSize = 10000, iterations = 10)
@Warmup(batchSize = 10000, iterations = 10)
@Fork(1)
public class StringConcatenationBenchmark {
    private static final String S = "some more data";
    private static final int maxLen = S.length()*10000;

    private String string;

    private StringBuilder stringBuilder;

    @Setup(Level.Iteration)
    public void setup() {
        string = "";
        stringBuilder = new StringBuilder();
    }

    @Benchmark
    public void stringConcatenation() {
        if(string.length() >= maxLen) string = "";
        string += S;
    }

    @Benchmark
    public void stringBuilderConcatenation() {
        if(stringBuilder.length() >= maxLen) stringBuilder = new StringBuilder();
        stringBuilder.append(S);
    }
}
以下是我的测试结果(i5 3340,4Gb RAM,64位Win7,JDK 1.8.045):

因此,您可以看到,对于
stringConcatenation
1e6/324878
)而言,只有大约3个批次适合第二个,而对于
stringBuilderConcatenation
而言,可以执行数千个批次,导致大量字符串导致
OutOfMemoryError

我不知道为什么添加更多内存对您不起作用,对我来说
-Xmx4G
足以运行原始基准的stringBuilder测试。可能您的长方体更快,因此生成的字符串更长。请注意,对于非常大的字符串,即使内存足够,也可以达到数组大小限制(20亿个元素)。添加内存后检查异常堆栈跟踪:是否相同?如果达到数组大小限制,它仍然是
OutOfMemoryError
,但是stacktrace会有一点不同。无论如何,即使有足够的内存,基准测试的结果也会不正确(对于
String
StringBuilder

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.MICROSECONDS)
@Measurement(batchSize = 10000, iterations = 10)
@Warmup(batchSize = 10000, iterations = 10)
@Fork(1)
public class StringConcatenationBenchmark {
    private static final String S = "some more data";
    private static final int maxLen = S.length()*10000;

    private String string;

    private StringBuilder stringBuilder;

    @Setup(Level.Iteration)
    public void setup() {
        string = "";
        stringBuilder = new StringBuilder();
    }

    @Benchmark
    public void stringConcatenation() {
        if(string.length() >= maxLen) string = "";
        string += S;
    }

    @Benchmark
    public void stringBuilderConcatenation() {
        if(stringBuilder.length() >= maxLen) stringBuilder = new StringBuilder();
        stringBuilder.append(S);
    }
}
Benchmark                   Mode  Cnt       Score       Error  Units
stringBuilderConcatenation  avgt   10     145.997 ±     2.301  us/op
stringConcatenation         avgt   10  324878.341 ± 39824.738  us/op