自动装箱的记忆和计算开销

自动装箱可能会产生大量内存开销。例如:

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

通常会占用大量内存(对于 6k 的实际数据,大约为 60kb)。

此外,盒装整数通常需要在内存中进行额外的往返,因此使 CPU 缓存效率降低。在上面的例子中,访问的存储器被分散到可能位于存储器的完全不同区域的五个不同位置:1。HashMap 对象,2。地图的 Entry[] table 对象,3。Entry 对象,4。entrys key 对象(拳击原始键),5。托管 value 对象(装箱原始值)。

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

boxed 需要两次内存访问,访问 primitive 只需一次。

从这张地图中获取数据时,看似无辜的代码

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

相当于:

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

通常,上面的代码会导致每个 Map#get(Integer) 操作的 Integer 对象的创建和垃圾收集。 (有关详细信息,请参阅下面的注释。)

为了减少这种开销,一些库提供原始类型那些优化的集合要求拳击。除了避免拳击开销之外,这些收集每个条目需要大约 4 倍的内存。虽然 Java Hotspot 可以通过处理堆栈上的对象而不是堆来优化自动装箱,但是不可能优化内存开销并导致内存间接。

Java 8 流还具有针对原始数据类型的优化接口,例如不需要装箱的 IntStream

注意:典型的 Java 运行时维护 IntegervalueOf 工厂方法使用的其他原始包装器对象的简单缓存,以及自动装箱。对于 Integer,此缓存的默认范围是 -128 到+127。某些 JVM 提供了一个 JVM 命令行选项,用于更改缓存大小/范围。