探究Java自動拆裝箱與Cache
目錄
什麼是拆裝箱
拆裝箱是Java1.5引入的新特性,它是基本資料型別與包裝型別的互相轉化。
裝箱:基本資料型別 => 包裝型別
拆箱:包裝型別 => 基本資料型別
JVM是如何實現拆裝箱
一般情況下我們是不需要自己手動做拆裝箱操作的,JVM會自動幫我們做。那麼JVM究竟是怎麼做的呢?我們通過例子去探究:
package zoro;
public class Test {
public static void main(String[] args) {
Integer i = 5;
int j = i;
}
}
從編碼的經驗告訴我們:
Integer i = 5,其實是JVM做了裝箱操作;
int j = i,則是做了拆箱的操作。
我們不妨使用javap -c,剖析一下位元組碼:
Compiled from "Test.java" public class zoro.Test { public zoro.Test(); Code: 0: aload_0 1: invokespecial #1 // Method java/lang/Object."<init>":()V 4: return public static void main(java.lang.String[]); Code: 0: iconst_5 1: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer; 4: astore_1 5: aload_1 6: invokevirtual #3 // Method java/lang/Integer.intValue:()I 9: istore_2 10: return }
從反編譯的資訊很明顯的看出例子中裝箱呼叫的是Integer的靜態方法valueOf(),而拆箱則呼叫了靜態方法intValue()。
相同套路的還有很多其他的包裝型別,如Byte、Short、Long、Float、Double……總結起來就是:裝箱使用valueOf(),拆箱使用xxxValue()。
聊聊IntegerCache
看以下例子:
@Test
public void integerCacheTest() {
Integer i1 = 100;
Integer i2 = 100;
Integer i3 = 200;
Integer i4 = 200;
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
}
在不瞭解裝箱的情況下,很多同學可能單純的以為int i的裝箱就是new Integer(i),然後蠻自信地回答兩個輸出都是false,因為通過new的物件分配在堆中,兩個物件即便有相同的值,但它們的引用是不等的。
執行結果告訴我們,這樣理解是不對的。
現在我們已經通過前面瞭解到了int裝箱到Integer使用的是valueOf(),我們不妨看看原始碼:
/**
* Returns an {@code Integer} instance representing the specified
* {@code int} value. If a new {@code Integer} instance is not
* required, this method should generally be used in preference to
* the constructor {@link #Integer(int)}, as this method is likely
* to yield significantly better space and time performance by
* caching frequently requested values.
*
* This method will always cache values in the range -128 to 127,
* inclusive, and may cache other values outside of this range.
*
* @param i an {@code int} value.
* @return an {@code Integer} instance representing {@code i}.
* @since 1.5
*/
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
註釋翻譯:
返回一個表示指定的 int 值的 Integer 例項。如果不需要新的 Integer 例項,則通常應優先使用該方法,而不是構造方法 Integer(int),因為該方法有可能通過快取經常請求的值而顯著提高空間和時間效能。
此方法將始終快取-128到127範圍內的值,
包含,並且可能快取此範圍之外的其他值。
說明Integer是有快取套路的,在快取範圍時取快取中的值,否則才new。從程式碼上看主要是依靠Integer的私有靜態內部類IntegerCache中的cache[]來實現快取的。
我們不妨看cache[]是在哪裡填充內容的:
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
*
* The cache is initialized on first usage. The size of the cache
* may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* During VM initialization, java.lang.Integer.IntegerCache.high property
* may be set and saved in the private system properties in the
* sun.misc.VM class.
*/
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() {}
}
註釋翻譯:
依JLS要求,快取以支援物件的自動裝箱,包括-128~127的值。
快取在第一次使用時初始化。快取的大小可以由xx:autoboxcachemax=size選項控制。
在虛擬機器初始化期間,java.lang.integer.integercache.high屬性可以設定並儲存在sun.misc.vm類的私有系統屬性中。
從程式碼上看快取的最低值:-128,最高值是127。最高值是可以通過系統屬性java.lang.integer.integercache.high設定的,如果未設定或低於127則取127,如果這值高於2147483518(Integer.MAX_VALUE - (-low) -1)則取2147483518。
IntegerCache.cache[]的預設儲存是:
cache[0] == -128
cache[1] == -127
……
cache[254] == 126
cache[255] == 127
故Integer.valueOf()為什麼要計算偏移量了:
return IntegerCache.cache[i + (-IntegerCache.low)];
現在回來看例子:
System.out.println(i1 == i2); // true
System.out.println(i3 == i4); // false
i1和i2的字面值都是100,這在IntegerCache的預設範圍[-128, 127]裡面,故都取自快取,它們的引用相等;而i3和i4的字面值是200,不在快取範圍,那就會使用new Integer(200)去裝箱,那i3和i4就是兩個堆中物件的引用,它們當然不相等。
其他包裝型別的Cache
除了包裝類Integer實現了IntegerCache之外,有其他包裝類實現了Cache嗎?
有,還有ByteCache、ShortCache、LongCache和CharacterCache等等。
它們的預設範圍分別是:
快取類 | 範圍 |
---|---|
ByteCache | [-128,127] |
ShortCache | [-128,127] |
IntegerCache | [-128,127] |
LongCache | [-128,127] |
CharacterCache | [0,127] |
除了IntegerCache可以改變範圍外,其他都不行。有興趣的可以看下原始碼。
你可能感興趣:
參考:
- 深入剖析Java中的裝箱和拆箱
- JDK1.8