Integer快取引發的思考
阿新 • • 發佈:2019-12-31
讀過JDK原始碼的朋友都知道,在基本型別的包裝類中,除了Float
和Double
以外,其餘都有快取機制:呼叫valueOf
方法時,如果引數值在快取區間內,則返回一個在類載入時就已建立好的物件,否則new
一個物件。這些包裝類的快取範圍如下:
// java.lang.Byte
// 快取區間:[-128,127]
public static Byte valueOf(byte b) {
final int offset = 128;
return ByteCache.cache[(int)b + offset];
}
// java.lang.Short
// 快取區間:[-128,127]
public static Short valueOf(short s) {
final int offset = 128;
int sAsInt = s;
if (sAsInt >= -128 && sAsInt <= 127) { // must cache
return ShortCache.cache[sAsInt + offset];
}
return new Short(s);
}
// java.lang.Integer
// 快取區間:[-128,127]
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
// java.lang.Long
// 快取區間:[-128,127]
public static Long valueOf(long l) {
final int offset = 128;
if (l >= -128 && l <= 127) { // will cache
return LongCache.cache[(int)l + offset];
}
return new Long(l);
}
// java.lang.Character
// 快取區間:[0,127]
public static Character valueOf(char c) {
if (c <= 127) { // must cache
return CharacterCache.cache[(int)c];
}
return new Character(c);
}
複製程式碼
我們知道,static
標註的欄位、方法、內部類和程式碼塊都是屬於類的資訊,會在類載入時初始化,而java.lang.Integer等基本類會在JVM啟動時就被BootstrapClassloader載入。因此,如果我們使用valueOf
方法獲取值在[-128,127]區間的Integer
物件,得到的實際上是JVM啟動時就已存在的Integer
陣列中的物件,而無需在Java堆記憶體中新建物件再獲取。
此處拿IntegerCache舉例,其餘包裝類快取機制雷同,在此便不贅述。
private static class IntegerCache {
static final int low = -128;
static final int high;
static final Integer cache[];
static {
// high value may be configured by property
int h = 127;
String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
i = Math.max(i,127);
// Maximum array size is Integer.MAX_VALUE
h = Math.min(i,Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
// If the property cannot be parsed into an int,ignore it.
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
for(int k = 0; k < cache.length; k++)
cache[k] = new Integer(j++);
// range [-128,127] must be interned (JLS7 5.1.7)
assert IntegerCache.high >= 127;
}
private IntegerCache() {}
}
複製程式碼
關於包裝類的快取機制,筆者還有兩點思考:為什麼要對較小的數字做快取?為什麼Float
和Double
沒有快取?
首先是第一個問題。筆者猜測,從統計學的角度來看,[-128,127]的數字和[0,127]的字元在程式碼中有較大的概率被使用到,因此JDK設計者為他們設計了快取機制,以免頻繁建立不必要的小物件。
其次是第二個問題。我們留意到只有整數和字元有快取,浮點數則沒有。包裝類中的這套快取機制,無非是用迴圈初始化了一個值域為[-128,127]或[0,127]的static final
陣列儲存在堆記憶體中(JDK 1.7及以後)。Java浮點數是根據 IEEE 754 實現的,顯而易見,根據現行的浮點數表示方法,根本不可能效仿整數和字元開闢一個長度較小的陣列快取部分浮點數。