Java Language Memory and Computational Overhead of Autoboxing


Example

Autoboxing can come at a substantial memory overhead. For example:

Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
    square.put(i, i * i); // Autoboxing of large integers
}

will typically consume substantial amount of memory (about 60kb for 6k of actual data).

Furthermore, boxed integers usually require additional round-trips in the memory, and thus make CPU caches less effective. In above example, the memory accessed is spread out to five different locations that may be in entirely different regions of the memory: 1. the HashMap object, 2. the map's Entry[] table object, 3. the Entry object, 4. the entrys key object (boxing the primitive key), 5. the entrys value object (boxing the primitive value).

class Example {
  int primitive; // Stored directly in the class `Example`
  Integer boxed; // Reference to another memory location
}

Reading boxed requires two memory accesses, accessing primitive only one.

When getting data from this map, the seemingly innocent code

int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
    sumOfSquares += square.get(i);
}

is equivalent to:

int sumOfSquares = 0;
for(int i = 256; i < 1024; i++) {
    sumOfSquares += square.get(Integer.valueOf(i)).intValue();
}

Typically, the above code causes the creation and garbage collection of an Integer object for every Map#get(Integer) operation. (See Note below for more details.)

To reduce this overhead, several libraries offer optimized collections for primitive types that do not require boxing. In addition to avoiding the boxing overhead, these collection will require about 4x less memory per entry. While Java Hotspot may be able to optimize the autoboxing by working with objects on the stack instead of the heap, it is not possible to optimize the memory overhead and resulting memory indirection.

Java 8 streams also have optimized interfaces for primitive data types, such as IntStream that do not require boxing.

Note: a typical Java runtime maintains a simple cache of Integer and other primitive wrapper object that is used by the valueOf factory methods, and by autoboxing. For Integer, the default range of this cache is -128 to +127. Some JVMs provide a JVM command-line option for changing the cache size / range.