Java自动装箱的内存和计算开销

例子

自动装箱可能会占用大量内存。例如:

Map<Integer, Integer> square = new HashMap<Integer, Integer>();
for(int i = 256; i < 1024; i++) {
    square.put(i, i * i); // 自动装箱大整数
}

通常会消耗大量的内存(6k的实际数据约为60kb)。

此外,带框整数通常需要在内存中进行额外的往返,因此会使CPU缓存的效率降低。在上面的示例中,所访问的内存被扩展到五个不同的位置,这些位置可能位于内存的完全不同的区域中:1.HashMap对象; 2。映射的Entry[] table对象; 3。Entry对象; 4。条目key对象(将原语装箱)键); 5. entrysvalue对象(装箱原始值)。

class Example {
  int primitive; // 直接存储在“ Example”类中
  Integer boxed; // 引用另一个存储位置
}

读取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();
}

典型地,上述代码使得创建和垃圾收集的的Integer每个对象的操作。(有关更多详细信息,请参见下面的注释。)Map#get(Integer)

为了减少这种开销,一些图书馆提供原始类型那些优化集合要求拳击。除了避免装箱开销外,这些集合每个条目所需的内存减少约4倍。尽管Java Hotspot可以通过使用堆栈而不是堆上的对象来优化自动装箱,但无法优化内存开销和由此产生的内存间接性。

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

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