Overview of Java Constant Pools
The term "constant pool" in Java most commonly refers to the runtime constant pool, which is part of the method area. There is exactly one runtime constant pool per JVM instance, shared across all running threads.
The constant pool stores data that is resolved during the compile phase. This includes values of final constants (member constants, local constants, and reference constants) as well as object literals.
During compilation, when a new constant is assigned a value, the JVM first checks if an identical value already exists in the constant pool. If it exists, the reference (memory address) of the existing entry is returned directly. If not, a new entry for the value is created in the pool before the reference is returned. This design guarantees that no duplicate values exist in the constant pool.
Final Constants
Any variable declared with the final keyword is treated as a constant. All final constents must be initialized at the time of declaration, otherwise the code will fail to compile.
Object Literals
An object literal is an object created by assigning a constant value directly, rather than instantiating a new object with the new keyword in the heap space.
The two most common types of object literals are primitive wrapper class literals and String object literals.
Primitive Wrapper Class Constant Pooling
Most of Java's primitive wrapper classes implement constant pool caching: Byte, Short, Integer, Long, Character, and Boolean all pre-cache values in the range [-128, 127] by default. Any value outside of this range will still create a new separate object. The two floating-point wrapper classes Float and Double do not implement constant pooling at all.
The example below demonstrates constant pool behavior with Integer:
Integer val1 = 40;
Integer val2 = 40;
Integer val3 = 0;
Integer val4 = new Integer(40);
Integer val5 = new Integer(40);
Integer val6 = new Integer(0);
System.out.println("val1 == val2: " + (val1 == val2));
System.out.println("val1 == val2 + val3: " + (val1 == val2 + val3));
System.out.println("val1 == val4: " + (val1 == val4));
System.out.println("val4 == val5: " + (val4 == val5));
System.out.println("val4 == val5 + val6: " + (val4 == val5 + val6));
System.out.println("40 == val5 + val6: " + (40 == val5 + val6));
Output:
val1 == val2: true
val1 == val2 + val3: true
val1 == val4: false
val4 == val5: false
val4 == val5 + val6: true
40 == val5 + val6: true
Explanation:
- When creating
Integer val1 = 40with a literal, the JVM first checks the constant pool for the value 40, and reuses the existing reference if it exists. - Declaring
new Integer(40)always allocates a brand new object in heap memory, regardless of existing constant pool entries. - For the expression
val4 == val5 + val6: the+operator cannot operate directly onIntegerobjects, soval5andval6are automatically unboxed to primitiveintvalues, which are then summed. When comparing anIntegerobject to a primitiveint, theIntegeris also unboxed, so the final comparison becomes40 == 40, which evaluates to true.
String Object Literals
String s1 = "abcd";
String s2 = new String("abcd");
System.out.println(s1 == s2); // false
String part1 = "str";
String part2 = "ing";
String full1 = "str" + "ing";
String full2 = part1 + part2;
System.out.println("string" == "str" + "ing"); // true
System.out.println(full1 == full2); // false
String full3 = "string";
System.out.println(full1 == full3); // true
Explanation:
- The reference
s1points directly to the entry for"abcd"stored in the constant pool, whilenew String("abcd")creates a newStringobject in heap memory. Any object created withnewwill always have a unique memory address. - For
+concatenation: only concatenation of multiple string literals (static text wrapped in quotes) is resolved at compile time, and the resulting string is added to the constant pool. - Concatenation involving string variables is resolved at runtime, the resulting new string is not added to the constant pool, so it has an independent memory address that does not match the precomputed constant pool entry.
The String.intern() Method
The intern() method can be used to force add a string to the constant pool:
public static void main(String[] args) {
String heapStr = new String("cloud computing");
String pooledStr = heapStr.intern();
String literalStr = "cloud computing";
System.out.println("heapStr == pooledStr? " + (heapStr == pooledStr));
System.out.println("literalStr == pooledStr? " + (literalStr == pooledStr));
}
Output:
heapStr == pooledStr? false
literalStr == pooledStr? true
Explanation:
intern() searches the constant pool for an existing string equal to the current string (checked via equals()). If a matching entry exists, it returns the reference of the existing entry. If no match exists, it adds the current string to the constant pool before returning its reference.
Note:
finalconstants require initialization at declaration, but object literals can be declared first and assigned later.
Benefits of Constant Pools
Constant pooling is designed to avoid performance degradation caused by frequent creation and destruction of identical objects, by enabling shared access to immutable constant values.
- Reduced memory usage: All identical constant values are merged into a single entry, so only one memory allocation is needed for repeated use of the same value.
- **Faster comparisons: Comparing references with
==is significantly faster than comparing values withequals(). If two constant references point to the same pool entry, their values are guaranteed to be equal.
Note: When used with primitive types,
==compares the actual value. When used with reference types,==compares the memory address of the object that the reference points to.