the integer cache
why -128..127, and how to make it bigger.
- chapter
- chapter 17.05
- reading time
- 7 min read
autoboxing an int calls Integer.valueOf, and valueOf hands out pre-made objects for -128 to 127 from a static array (IntegerCache). inside that range two boxes of the same value are the same object, so == "works". outside it you get a new object each time and == is false. the upper end can be raised with -XX:AutoBoxCacheMax; nothing else about it is configurable.
the picture
the wrappers chapter shows the symptom: 127 == 127 but 128 != 128. this one opens Integer.java and looks at the array that causes it.
think of a coffee shop with a shelf of pre-filled cups for the 256 most common orders. ask for one of those and you get the cup off the shelf, the same physical cup everyone else who ordered it is holding (it's immutable, so sharing is fine). ask for anything else and they make you a fresh one. two fresh cups of the same order are still two cups.
- 1void main() {
- 2 Integer a = 127;
- 3 Integer b = 127;
- 4 Integer c = 128;
- 5 Integer d = 128;
- 6 IO.println(a == b);
- 7 IO.println(c == d);
- 8 IO.println(c.equals(d));
- 9}
·four boxed ints coming up. each line compiles to Integer.valueOf(...).
a and b share one cached Integer; c and d each get their own. the values are equal in both cases. the objects aren't.valueOf, from the source
Integer a = 127 isn't magic. javac rewrites boxing and unboxing into plain method calls:
$ javap -c Box # Integer a = 127; int x = a;
0: bipush 127
2: invokestatic #7 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_0
6: aload_0
7: invokevirtual #13 // Method java/lang/Integer.intValue:()Iand valueOf in jdk 25 is four lines. in range? return the pre-made object at index i + 128 of the cache array. out of range? new Integer(i). the cache is a nested class with a static initializer, so it's built the first time anything touches it (and on jdk 25 its objects can even come pre-made from the CDS archive, which is why the source has an archivedCache field).
a == b is true because both are the same object. one step outside it's false, and equals doesn't care either way.var votes = new HashMap<String, Integer>();
for (int i = 0; i < 100; i++) { votes.merge("ada", 1, Integer::sum); votes.merge("bob", 1, Integer::sum); }
IO.println(votes.get("ada") == votes.get("bob"));
for (int i = 0; i < 100; i++) { votes.merge("ada", 1, Integer::sum); votes.merge("bob", 1, Integer::sum); }
IO.println(votes.get("ada") == votes.get("bob"));why -128 to 127
not a JDK whim. the language spec (JLS §5.1.7, boxing conversion) requires boxing to give back the identical object for small values: true and false, a char up to \u007f, and whole numbers from -128 to 127. the source even says so in a comment right after building the array. so the JDK can make the cache bigger, but never smaller, and the lower bound is fixed at -128.
here's every wrapper in jdk 25, read from its valueOf:
| type | cached range | configurable? |
|---|---|---|
Integer | -128 to 127 | the upper bound only: -XX:AutoBoxCacheMax=N |
Short | -128 to 127 | no |
Byte | all 256 values | no (there's nothing left to cache) |
Long | -128 to 127 | no. 128L == 128L is false even with the flag |
Character | \u0000 to \u007f (0 to 127) | no |
Boolean | both: TRUE and FALSE | no |
Float, Double | nothing. valueOf is just new | no |
void main() {
Integer a = 127, b = 127, c = 128, d = 128;
IO.println("Integer 127: " + (a == b) + ", 128: " + (c == d));
Long l1 = 127L, l2 = 127L, l3 = 128L, l4 = 128L;
IO.println("Long 127: " + (l1 == l2) + ", 128: " + (l3 == l4));
Character c1 = 127, c2 = 127, c3 = 128, c4 = 128;
IO.println("Character 127: " + (c1 == c2) + ", 128: " + (c3 == c4));
Double d1 = 1.0, d2 = 1.0;
IO.println("Double 1.0: " + (d1 == d2));
}Integer 127: true, 128: false Long 127: true, 128: false Character 127: true, 128: false Double 1.0: false
making it bigger
two spellings, same effect, and they only move the top:
$ java -XX:AutoBoxCacheMax=1000 Cache
Integer 127: true, 128: true, 1000: true
Long 127: true, 128: false
$ java -Djava.lang.Integer.IntegerCache.high=1000 Cache
Integer 127: true, 128: true, 1000: true
$ java -XX:AutoBoxCacheMax=50 Cache # below 127 is ignored
Integer 127: true, 128: false, 1000: false-XX:AutoBoxCacheMax is listed as a C2 flag (its default prints as 128 in -XX:+PrintFlagsFinal), but all it really does is pass the system property to IntegerCache, so it worked the same with -Xint and with C1 only. when it isn't set, the property isn't set either, and the cache stops at 127.
should you? rarely. it costs one 16-byte Integer per value (plus a slot in the array) on the heap for the life of the JVM, and it only helps code that boxes the same small-ish numbers over and over. if a profile shows millions of short-lived Integers, the better fix is usually not boxing at all: int[] instead of List<Integer>, primitive streams, or a primitive collection library.
the traps that aren't ==
==between anIntegerand anintunboxes.Integer x = 1000; x == 1000is a numeric comparison and istrue. it's onlyInteger == Integerthat compares objects.new Integer(...)is deprecated (since java 9). it always allocates and skips the cache.Integer.valueOfor plain autoboxing is the same code, minus the waste.- the JIT can remove boxes entirely.
valueOfis an intrinsic, and C2'sEliminateAutoBoxplus escape analysis can delete a box that never escapes. so "boxing is slow" is sometimes true and sometimes compiled away; measure it, don't assume it. - identity might stop mattering. project Valhalla plans to turn the wrapper classes into value classes, where
==compares values. that's future work, see valhalla. on jdk 25, identity is very much real.
- autoboxing is
Integer.valueOf, unboxing isintValue(). javac writes the calls for you. valueOfreturns cached objects for -128..127, new ones everywhere else.- the range comes from the JLS. you can raise the top with
-XX:AutoBoxCacheMax, never lower anything. Long,ShortandCharactercache small values too;FloatandDoublenever do.Integer == Integercompares objects. useequals, or unbox.