自動裝箱的記憶和計算開銷
自動裝箱可能會產生大量記憶體開銷。例如:
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 執行時維護 Integer
和 valueOf
工廠方法使用的其他原始包裝器物件的簡單快取,以及自動裝箱。對於 Integer
,此快取的預設範圍是 -128 到+127。某些 JVM 提供了一個 JVM 命令列選項,用於更改快取大小/範圍。