Avoiding Dead-Code Elimination
Blackhole and state.
Avoiding Dead-Code Elimination is a free Java Academy lesson on CoddyKit — lesson 4 of 4. You can read the complete lesson below for free — then practise it hands-on in the browser with a built-in code editor and a 24/7 AI tutor. It is part of the Java Academy learning path, one of 4 lessons in the course, and your progress syncs across the web and the CoddyKit app.
Avoiding Dead-Code Elimination
The single biggest microbenchmark trap is dead-code elimination (DCE): if the JIT proves a result is never used, it deletes the computation. Your benchmark then measures nothing. JMH gives you two tools to prevent this — returning values and the Blackhole.
The DCE Trap
This loop computes a square root a million times but never uses the result. A smart compiler can delete the entire loop.
public class Main {
public static void main(String[] args) {
long t = System.nanoTime();
for (int i = 0; i < 1_000_000; i++) {
double ignored = Math.sqrt(i); // result thrown away
}
System.out.println("ns: " + (System.nanoTime() - t));
}
}Fix 1: Return the Result
The easiest defense is to return the computed value from the @Benchmark method. JMH consumes every returned value, so the JVM cannot prove it is dead.
import org.openjdk.jmh.annotations.Benchmark;
public class Bench {
@Benchmark
public double sqrt() {
return Math.sqrt(42.0); // returned -> consumed by JMH
}
}Fix 2: The Blackhole
When a benchmark produces multiple values or you cannot return one, inject a Blackhole and call consume(...). The Blackhole convinces the JIT each value is used, without the cost of real work.
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
public class Bench {
@Benchmark
public void manyValues(Blackhole bh) {
for (int i = 0; i < 100; i++) {
bh.consume(Math.sqrt(i));
}
}
}@State Objects
A @State class holds inputs that are opaque to the optimizer, defeating constant folding. JMH instantiates it and passes it to your benchmark. Scope can be Thread, Benchmark (shared), or Group.
import org.openjdk.jmh.annotations.*;
public class Bench {
@State(Scope.Thread)
public static class Data {
public int x = 21;
}
@Benchmark
public int multiply(Data d) {
return d.x * 2; // d.x is not a compile-time constant
}
}@Setup and @TearDown
Inside a @State class, methods annotated with @Setup run before measurement and @TearDown after. The Level controls frequency: Trial (once), Iteration, or Invocation.
import org.openjdk.jmh.annotations.*;
@State(Scope.Thread)
public class Data {
int[] arr;
@Setup(Level.Trial)
public void init() {
arr = new int[1000];
for (int i = 0; i < arr.length; i++) arr[i] = i;
}
}Avoid Constant Inputs
Never feed literal constants to the method under test. Math.sqrt(2.0) may be folded to a constant; Math.sqrt(state.value) cannot. Always source inputs from a @State field.
Returning Multiple Values
If your benchmark naturally produces two results, you can return one and consume the other through a Blackhole, or combine them. Leaving either unconsumed reopens the DCE hole.
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.infra.Blackhole;
public class Bench {
@Benchmark
public int twoResults(Blackhole bh) {
int a = 3 * 7;
int b = 5 + 9;
bh.consume(b);
return a;
}
}Blackhole Overhead
The Blackhole is designed to be extremely cheap and itself resistant to optimization, but it is not entirely free. For ultra-fast operations, prefer returning a single value; reserve Blackhole for loops and multi-value cases.
Sanity-Check Your Numbers
If a benchmark reports an impossibly low time (sub-nanosecond for real work), suspect DCE or constant folding. Re-check that results are returned or consumed and that inputs come from @State.
Putting It Together
A robust benchmark: read inputs from a @State object, do the work, and either return the result or Blackhole.consume it. With those three habits, DCE and constant folding cannot corrupt your measurements.
Quick Check
Test your understanding of defeating dead-code elimination.
Recap
You learned to keep benchmarks honest:
- Dead-code elimination deletes unused computations.
- Return a single result so JMH consumes it.
- Use Blackhole.consume for loops and multiple values.
- @State objects keep inputs opaque, defeating constant folding.
- @Setup / @TearDown prepare and clean up state at a chosen Level.
Frequently asked questions
Is the “Avoiding Dead-Code Elimination” lesson free?
Yes — the full text of “Avoiding Dead-Code Elimination” is free to read here on the web, and the Java Academy course includes 4 lessons in total. To practise it interactively (a built-in code editor and a 24/7 AI tutor) and unlock the rest of the Java Academy course, upgrade to CoddyKit PRO.
What will I learn in “Avoiding Dead-Code Elimination”?
Blackhole and state. You practise Java Academy with hands-on code you run directly in the browser, and a 24/7 AI tutor answers your questions as you work through the lesson.
Do I need any experience to start Java Academy?
No prior experience is required. Java Academy on CoddyKit is structured for beginners through advanced learners; this is — lesson 4 of 4, so you can start here or from the beginning and move at your own pace.
How long does the “Avoiding Dead-Code Elimination” lesson take?
Most CoddyKit lessons take about 5–10 minutes. Each one is bite-sized and interactive, so you make steady progress and pick up exactly where you left off across the web and the app.
Can I write and run code in this Java Academy lesson?
Yes. Every Java Academy lesson includes a built-in code editor, so you write and run real code right in your browser and get instant AI feedback — no local setup required.
All lessons in this course
- Why JMH
- Writing a Benchmark
- Warmup and Iterations
- Avoiding Dead-Code Elimination