Skip to the content.
blog talks comics about

Java Integer Caching: why and how

As Java developers, we know that we shouldn’t compare objects using the == operator because this way we compare the references and not the actual values. That would also apply to Integer objects:

Integer x = 256;
Integer y = 256;
System.out.println(x == y); // false

This result can surprise nobody.

However, Java has a little trick for us:

Integer x = 127;
Integer y = 127;
System.out.println(x == y); // true

what is going on?

We are observing a mechanism called Integer Caching, which is one of the tricks that Java has for us in order to improve performance and memory usage.

Normally, when we create an Integer object, we claim some space on the heap and put the object there — for example, with the value 256. When we create another Integer object with the same value, we allocate some more space on the heap and put there another Integer with value 256, even though such an object was already created previously.

The idea behind Integer Caching is that certain small integer values are used a lot more often than others. For example, we often use small integers as default values, counters etc. We could argue that they are usually primitives, but often primitives are wrapped into Integer objects, for example if we store them in a List.

When the Integer class is loaded, Java caches all Integer objects with values in range [-128, 127] inclusive. Every time we create an Integer object, Java checks for us if this value is within the range, and if so, it’s not allocated but taken from the cache instead.

Two things are achieved this way:

  • our application becomes faster because it doesn’t need to do all the work for extra value allocation;
  • potentially, less space is used because these values are stored only once.

how does this happen exactly?

This simple piece of code Integer x = 127; is going to become the following in the bytecode:

 0: bipush        127
 2: invokestatic  #7     // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
 5: astore_1

First operation is bipush, meaning that we take the byte value 127 and put it onto the operand stack as an integer. Next, we see invokestatic, which calls a static method Integer.valueOf that accepts a primitive int value and returns an Integer object. Finally, there is an astore, which simply stores the resulting value as a variable.

What is the most interesting for us is that during compilation with javac, the code

Integer x = 127;

is converted into

Integer x = Integer.valueOf(127);

The range checks are happening inside of that Integer.valueOf method.

This is how it looks:

// IntegerCache.low = -128;
// IntegerCache.high = 127;
public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}

The cache itself is a trivial array of Integer, and it’s populated when Integer class is loaded.

static final Integer[] cache;

Since it’s a static final array, it’s kept around for as long as the Integer class is loaded, so pretty much for the lifetime of your application.

what if this range is insufficient?

If you know you have a specific use-case where you’re often reusing Integer values of a bigger range, say [-1000, 1000], you can ask Java to cache a different range for you. This can be achieved by running your app with a flag: -XX:AutoBoxCacheMax=1000.

With all the benefits described above, one might wonder: why not cache more values by default, or even all of them? Performance wins, less space is taken, so why not?

Well, if we imagine caching the whole range of integers, it would result in storing over 4 billion objects! They are not garbage collected, so we will have this large space occupied permanently. JVM will have difficulties managing it efficiently. In summary, the cost of having such an enormous cache defeats the memory and performance benefits gained from it.

summary

Java does a lot of things that are designed to sneakily improve performance and memory usage, and the Integer Cache is one of them. A small range of Integer objects (the ones that are considered frequently used) is stored all the time, so we don’t need to reallocate these objects again and again. The range is small, so it doesn’t occupy too much space and overwhelm the JVM.

back to blog
Hits