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

时间:2018-07-08 00:29:48

标签: java benchmarking microbenchmark jmh

我正在学习如何使用JMH对事物进行微基准测试。我从看似简单的内容开始:StringBuilderString +=的字符串连接。

根据我的理解,我应该制作一个包含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();
    }

}

它有效,但是我在想-我不想在每次迭代结束时调用.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)

我了解到,如果JMH尽可能快地附加到StringBuilder,我将很快耗尽内存,因此对OutOfMemoryError问题并不感到惊讶。但是我不明白为什么builder.toString()会解决它。

所以我的问题是:

  • 为什么builder.toString()可以避免OutOfMemoryError问题? StringBuilder仍然不把所有字符都保留在内存中吗?

  • 假设我既不希望StringBuilder的构造函数也不希望它的.toString()方法成为基准测试的一部分,那么如何正确编写此测试?

1 个答案:

答案 0 :(得分:5)

调用toString()会花费一些时间,并产生垃圾,需要运行GC,从而进一步降低了代码的速度。

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