Constant Folding in the JVM
What is Constant Folding?
Constant folding is a compiler optimization technique where the JVM evaluates constant expressions at compile time rather than computing them at runtime. When the compiler detects that an expression will always yield the same result, it replaces the expression with its computed value.
Simple examples
Basic arithmetic
// Before constant folding
int value = 60 * 24 * 365;
// After constant folding (what actually goes into the bytecode)
int value = 525600; // Computed at compile time
String concatenation
// Before constant folding
String message = "Hello" + " " + "World";
// After constant folding
String message = "Hello World"; // Single constant in bytecode
When does it happen?
Compile-time constants
public class ConstantExample {
// Computed at compile time
private static final int MINUTES_IN_YEAR = 60 * 24 * 365;
// Not computed at compile time (requires runtime information)
private static final int DYNAMIC_VALUE = Math.random() > 0.5 ? 1 : 2;
}
public class ConstantFolding {
// This method's result will be constant folded
static final int computeMaxAge() {
return 60 * 24 * 365; // Will be folded to 525600
}
}
Final fields
public class FinalExample {
// Can be constant folded
private final int CONSTANT = 100 * 100;
// Cannot be constant folded (value known only at construction)
private final int constructorValue;
public FinalExample(int value) {
this.constructorValue = value * 100;
}
}
Complex examples
Mathematical expressions
public class MathExample {
// Will be folded
private static final double CIRCLE_RADIANS = 2 * Math.PI;
// Will NOT be folded (Math.sin is not a constant expression)
private static final double SINE_VALUE = Math.sin(Math.PI / 2);
}
String operations
public class StringExample {
// Will be folded
private static final String GREETING = "Hello".toUpperCase() + "!";
// Will NOT be folded (depends on runtime locale)
private static final String LOCALE_GREETING =
"Hello".toLowerCase(Locale.getDefault());
}
Verifying constant folding
We can use javap
to see the bytecode and verify constant folding:
javac YourClass.java
javap -c YourClass
Before we go into exploring byte-code, let’s look at the list of instructions that are being used to load constants: Understanding JVM Constant Loading Instructions.
Example without Constant Folding
public class NoFolding {
public int calculate(int x) {
return x * 60 * 24; // Not folded because x is variable
}
}
Example with Constant Folding
public class WithFolding {
public int getMinutesInDay() {
return 60 * 24; // Folded to 1440 in bytecode
}
}
sipush
- Pushes a short integer onto the operand stack1440
- The constant value being pushed (in this case,60 * 24
which was constant folded!)
Example: static vs static final
class Final {
private static final int A_NUMBER = 2 * 2 * 2;
private static int secondsInDay = 24 * 60 * 60;
public void printValues() {
System.out.println(A_NUMBER); // Will directly use 86400
System.out.println(secondsInDay); // Will load from field
}
}
A_NUMBER
constant, it doesn't even appear in the bytecode! This is because it's a static final
that's computed at compile time, so its value (86400
) is directly embedded wherever the constant is used.
For secondsInDay
, we see:
- The computation (
24 * 60 * 60
) was constant folded to86400
- The value needs to be loaded (
ldc
) and stored (putstatic
) in the field because it's not final - It appears in the static initializer block (
static {}
) because it's a static field
Benefits
Improved Performance
- No runtime calculation needed
- Reduced instruction count
- Better CPU cache utilization (negligible imact)
Reduced Code Size
- Multiple operations replaced with single constant
- Smaller bytecode