为什么.toString()似乎修复了StringBuilder的OutOfMemoryError异常?

JVo*_*Von 3 java benchmarking microbenchmark jmh

我正在学习如何使用JMH对事物进行微观标记.我开始看似简单的东西:StringBuildervs的字符串连接String +=.

根据我的理解,我应该创建一个State包含一个实例的对象,StringBuilder因为我不想对它的构造函数进行基准测试(我也不想每次迭代都是空的).同样适用于String +=测试 - 我希望我的String对象State与新字符串连接.

这是我的代码:

@State(Scope.Thread)
@BenchmarkMode(Mode.AverageTime)
@OutputTimeUnit(TimeUnit.NANOSECONDS)
public class Test {

    @State(Scope.Thread)
    public static class BenchmarkState {

        public StringBuilder    builder;
        public String           regularString;

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

    }

    @Benchmark
    public String stringTest(BenchmarkState state) {
        state.regularString += "hello";
        return state.regularString;
    }

    @Benchmark
    public String stringBuilderTest(BenchmarkState state) {
        state.builder.append("hello");
        return state.builder.toString();
    }

    public static void main(String[] args) throws RunnerException {
        Options opt = new OptionsBuilder()
                .include(Test.class.getSimpleName())
                .forks(1)
                .timeUnit(TimeUnit.MILLISECONDS)
                .mode(Mode.Throughput)
                .measurementTime(TimeValue.seconds(10))
                .build();

        new Runner(opt).run();
    }

}
Run Code Online (Sandbox Code Playgroud)

它有效,但我在思考 - 我不想.toString()在每次迭代结束时调用它.我只测试连接.所以我决定通过返回来删除它null.

但是,这发生在第一次预热迭代期间:

java.lang.OutOfMemoryError: Java heap space
    at java.util.Arrays.copyOf(Arrays.java:3332)
    at java.lang.AbstractStringBuilder.ensureCapacityInternal(AbstractStringBuilder.java:124)
    at java.lang.AbstractStringBuilder.append(AbstractStringBuilder.java:448)
    at java.lang.StringBuilder.append(StringBuilder.java:136)
Run Code Online (Sandbox Code Playgroud)

我知道如果JMH StringBuilder尽可能快地追加,我会很快耗尽内存,所以我对这个OutOfMemoryError问题并不感到惊讶.但我不明白为什么要builder.toString()修复它.

所以我的问题是:

  • 为什么要builder.toString()避免出现OutOfMemoryError问题?是否StringBuilder仍然将所有字符保留在内存中?

  • 假设我既不想要StringBuilder构造函数也不想让它的 .toString()方法成为基准测试的一部分,那么我该如何正确地编写这个测试呢?

And*_*eas 5

调用toString()需要时间,并生成垃圾,需要GC运行,进一步减慢代码.

由于测试有时间限制,这些减速可能导致测试在消耗所有内存之前停止.如果你增加时间限制,代码可能会因为OOM而失败,即使使用OOM toString,也只会花费更长的时间.

  • @JasonSperske睡觉?一点都不.你为什么认为我这么说?我说代码会做更多(执行`toString`)并且它会产生更多垃圾,需要JVM更频繁地收集垃圾(即*做更多*),**减慢**代码,即降低*性能*的代码.如果你监视CPU,你会发现没有睡眠. (3认同)