Search code examples
javabenchmarkingmicrobenchmarkjmh

OutOfMemoryError when benchmarking ArrayList.add() in JMH


I've been attempting to run benchmarks to compare how many ops/ms for the add method of ArrayLists and LinkedLists in Java. My benchmarks are set up as follows but I can't pinpoint what is causing the out of memory error. Possibly the initialisation of the lists in the setup method?

@State(Scope.Benchmark)
@BenchmarkMode(Mode.Throughput)
@Warmup(iterations = 5, time = 5)
@Measurement(iterations = 10, time = 5)
@OutputTimeUnit(TimeUnit.MILLISECONDS)
public class ListAddBenchmark {

    private int NUMBER = 123;

    private List<Integer> arrayList, linkedList;

    @Param({"1000","100000","1000000"})
    public int iterations;

    @Setup(Level.Trial)
    public void setup() {
        arrayList = new ArrayList<>();
        linkedList = new LinkedList<>();
    }

    @Benchmark
    public boolean arrayListBenchmark() {
        return arrayList.add(NUMBER);
    }

    @Benchmark
    public boolean linkedListBenchmark() {
        return linkedList.add(NUMBER);
    }
}

Here is the output I am getting...

# JMH version: 1.32
# VM version: JDK 11.0.11, OpenJDK 64-Bit Server VM, 11.0.11+9-Ubuntu-0ubuntu2
# VM invoker: /usr/lib/jvm/java-11-openjdk-amd64/bin/java
# VM options: -Xmx4G -Xms2G
# Blackhole mode: full + dont-inline hint
# Warmup: 5 iterations, 5 s each
# Measurement: 10 iterations, 5 s each
# Timeout: 10 min per iteration
# Threads: 1 thread, will synchronize iterations
# Benchmark mode: Throughput, ops/time
# Benchmark: dev.example.benchmarks.collections.lists.ListAddBenchmark.arrayListBenchmark
# Parameters: (iterations = 1000)

# Run progress: 0.00% complete, ETA 00:22:30
# Fork: 1 of 3
# Warmup Iteration   1: 22716.118 ops/ms
# Warmup Iteration   2: 16060.593 ops/ms
# Warmup Iteration   3: 16104.508 ops/ms
# Warmup Iteration   4: <failure>

java.lang.OutOfMemoryError: Java heap space
    at java.base/java.util.Arrays.copyOf(Arrays.java:3689)
    at java.base/java.util.ArrayList.grow(ArrayList.java:238)
    at java.base/java.util.ArrayList.grow(ArrayList.java:243)
    at java.base/java.util.ArrayList.add(ArrayList.java:486)
    at java.base/java.util.ArrayList.add(ArrayList.java:499)
    at dev.example.benchmarks.collections.lists.ListAddBenchmark.arrayListBenchmark(ListAddBenchmark.java:60)
    at dev.example.benchmarks.collections.lists.jmh_generated.ListAddBenchmark_arrayListBenchmark_jmhTest.arrayListBenchmark_thrpt_jmhStub(ListAddBenchmark_arrayListBenchmark_jmhTest.java:142)
    at dev.example.benchmarks.collections.lists.jmh_generated.ListAddBenchmark_arrayListBenchmark_jmhTest.arrayListBenchmark_Throughput(ListAddBenchmark_arrayListBenchmark_jmhTest.java:83)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.base/java.lang.reflect.Method.invoke(Method.java:566)
    at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:470)
    at org.openjdk.jmh.runner.BenchmarkHandler$BenchmarkTask.call(BenchmarkHandler.java:453)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:515)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)

Could someone help with what I'm doing incorrectly?


Solution

  • I've managed to curb my OutOfMemoryError by manually calling System.gc() on every teardown iteration. Thanks to @majusebetter for your help in identifying cause.

    My final code now looks like:

    @State(Scope.Benchmark)
    @BenchmarkMode(Mode.Throughput)
    @Warmup(iterations = 5, time = 500, timeUnit = TimeUnit.MILLISECONDS)
    @Measurement(iterations = 10, time = 500, timeUnit = TimeUnit.MILLISECONDS)
    @OutputTimeUnit(TimeUnit.MILLISECONDS)
    public class ListAddBenchmark {
    
        private int NUMBER = 123;
    
        private List<Integer> arrayList, linkedList;
    
        @Setup(Level.Iteration)
        public void setup() {
            arrayList = new ArrayList<>();
            linkedList = new LinkedList<>();
        }
    
        @TearDown(Level.Iteration)
        public void teardown() {
            System.gc();
        }
    
        @Benchmark
        public boolean arrayListBenchmark() {
            return arrayList.add(NUMBER);
        }
    
        @Benchmark
        public boolean linkedListBenchmark() {
            return linkedList.add(NUMBER);
        }
    }
    

    The final output (without any memory issues) is:

    Benchmark                              Mode  Cnt      Score     Error   Units
    ListAddBenchmark.arrayListBenchmark   thrpt   30  38587.191 ± 722.036  ops/ms
    ListAddBenchmark.linkedListBenchmark  thrpt   30  13955.916 ± 574.261  ops/ms