Understanding Java Integer Caching and Autoboxing: When == Can Mislead
This article explains Java's Integer caching mechanism, why using == to compare wrapper objects can produce unexpected results, how the IntegerCache works, how to adjust its range with JVM options, and demonstrates the behavior through code examples and javap decompilation.
Alibaba's development handbook (section 4) states that all comparisons between integer wrapper objects should use equals rather than == because values outside the range -128 to 127 are not cached and will be distinct objects.
Consider the following code snippet:
public class IntegerTest {
public static void main(String[] args) {
Integer a = 100,
b = 100,
c = 200,
d = 200;
System.out.println(a == b);
System.out.println(c == d);
}
}The output is:
true
falseThe reason is that the JVM caches Integer objects only for values between -128 and 127. Values outside this range are allocated on the heap, so == compares references and returns false.
The caching logic resides in Integer.valueOf(int i) :
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}Developers can enlarge the cache upper bound with the JVM option -XX:AutoBoxCacheMax=<size> , which changes IntegerCache.high and can improve performance when frequently using large constant Integers.
To see the exact bytecode, you can use IDEA's external tool configuration to run javap -c on the compiled class. The relevant bytecode lines (2, 8, 15, 22) invoke Integer.valueOf , confirming that autoboxing always calls this method.
/Library/Java/JavaVirtualMachines/jdk1.8.0_101.jdk/Contents/Home/bin/javap -c IntegerTest.class
Compiled from "IntegerTest.java"
public class com.github.codedrinker.basic.IntegerTest {
public com.github.codedrinker.basic.IntegerTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 100
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 100
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: sipush 200
15: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: astore_3
19: sipush 200
22: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
25: astore 4
27: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
30: aload_1
31: aload_2
32: if_acmpne 39
35: iconst_1
36: goto 40
39: iconst_0
40: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
43: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_3
47: aload 4
49: if_acmpne 56
52: iconst_1
53: goto 57
56: iconst_0
57: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
60: return
}The same caching behavior applies to other wrapper types such as Character , Long , and Short , which also have their own cache ranges.
Understanding this mechanism helps avoid subtle bugs in Java code and can be leveraged for performance tuning.
Full-Stack Internet Architecture
Introducing full-stack Internet architecture technologies centered on Java
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.