Integer快取範圍到底是多少?
本文主要大致思路為:
不管從工作中還是面試,這篇文章都應該好好看完,本人認為是非常有用的。
案例
Integer是基本型別int的封裝類。平時不管是入坑多年的小夥伴還在入坑路上的小夥伴,都應該知道的使用頻率是相當高。
下面模仿訂單支付,做了一個訂單支付狀態列舉類PayStatusEnum
public class IntegerDemo { public static void main(String[] args) { Integer a = new Integer(8); Integer b = Integer.valueOf(8); Integer c = 8; System.out.println(a.equals(b)); System.out.println(a.equals(c)); System.out.println(b.equals(c)); System.out.println(a == b); System.out.println(a == c); System.out.println(b == c); } }
結果輸出什麼?
把上面程式碼中的8改成128後,又輸出什麼?
public class IntegerDemo { public static void main(String[] args) { Integer a = new Integer(128); Integer b = Integer.valueOf(128); Integer c = 128; System.out.println(a.equals(b)); System.out.println(a.equals(c)); System.out.println(b.equals(c)); System.out.println(a == b); System.out.println(a == c); System.out.println(b == c); } }
答案慢慢道來。
解析案例
Integer整體閱覽
構造方法
private final int value;
public Integer(int value) {
this.value = value;
}
太簡單了,沒什麼可講的。
valueOf()方法
public static Integer valueOf(String s) throws NumberFormatException { return Integer.valueOf(parseInt(s, 10)); } //@HotSpotIntrinsicCandidate 這個註解是JDK9才引入的 //HotSpot 虛擬機器將對標註了@HotSpotIntrinsicCandidate註解的方法的呼叫, //替換為直接使用基於特定 CPU 指令的高效實現。這些方法我們便稱之為 intrinsic。 public static Integer valueOf(int i) { //如果i在low和high之間就使用快取 if (i >= IntegerCache.low && i <= IntegerCache.high){ return IntegerCache.cache[i + (-IntegerCache.low)]; } return new Integer(i); }
上面valueOf()方法中用到了IntegerCache,下面我們來聊聊。
IntegerCache
下面是IntegerCache原始碼和部分註釋:
/**
* Cache to support the object identity semantics of autoboxing for values between
* -128 and 127 (inclusive) as required by JLS.
* JLS協議要求快取在-128到127之間(包含邊界值)
*
* The cache is initialized on first usage.
* 程式第一次使用Integer的時候
* The size of the cache may be controlled by the {@code -XX:AutoBoxCacheMax=<size>} option.
* JVM 的啟動引數 -XX:AutoBoxCacheMax=size 修改最大值
* 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.
* 可以通過系統屬性來獲得:-Djava.lang.Integer.IntegerCache.high=<size>
*/
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");
//如果有配置-XX:AutoBoxCacheMax=<size>
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
//和127進行比較,誰大用誰
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.
// 如果該值配置錯誤則忽略該引數配置的值,使用預設範圍-128~127
}
}
high = h;
cache = new Integer[(high - low) + 1];
int j = low;
// 快取通過for迴圈來實現,建立範圍內的整數物件並存儲到cache陣列中
// 程式第一次使用Integer的時候需要一定的額外時間來初始化該快取
for(int k = 0; k < cache.length; k++){
cache[k] = new Integer(j++);
}
//無論如何,快取的最大值肯定是大於等於127
assert IntegerCache.high >= 127;
}
//私有的構造方法,因為所有的屬性均屬於類常量
private IntegerCache() {}
}
整個靜態塊流程:
那麼,如何設定java.lang.Integer.IntegerCache.high的值呢?
The size of the cache may be controlled by the {@code -XX:AutoBoxCacheMax=
註釋中已經說清楚,可以使用-XX:AutoBoxCacheMax=
寫個demo來debug看看
public class IntegerDemo {
public static void main(String[] args) {
Integer a = 8;
Integer b =Integer.valueOf(8);
System.out.println(a.equals(b));
System.out.println(a == b);
}
}
設定-XX:AutoBoxCacheMax=100
開始debug
看看high的值
是127,那就對了,因為上面
設定-XX:AutoBoxCacheMax=130
開啟debug模式
注意:low=-128是不會變的,整個快取初始化過程並沒有對low進行修改,再說low是常量。
-XX:AutoBoxCacheMax最大能設定成多大?
因為Integer的最大值是2147483647 ,所以我們這裡使用這個值試試,
開始debug,直接報OOM了
為什麼會OOM呢?
如果-XX:AutoBoxCacheMax沒有設定值,那麼對應陣列是這樣的。
equals()方法
上面的案例中有equals方法,這裡把這個方法也拿出來聊聊
private final int value;
public boolean equals(Object obj) {
if (obj instanceof Integer) {
//這裡比較的是兩個int型別的值
return value == ((Integer)obj).intValue();
}
//obj不是Integer型別直接返回false
return false;
}
回到上面的案例中
當我們使用equals方法比較兩個物件是否相等的時候其實就是比較他們的value值。
所以不管是128還是8,equals後肯定都是true。
當引用型別使用==進行比較的時候,此時比較的是兩個引用的物件的地址,是不是同一個。
public class IntegerDemo {
public static void main(String[] args) {
Integer a = new Integer(8);
Integer b = Integer.valueOf(8);
Integer c = 8;
System.out.println(a.equals(b));
System.out.println(a.equals(c));
System.out.println(b.equals(c));
System.out.println(a == b);
System.out.println(a == c);
System.out.println(b == c);
}
}
我們看看器class檔案中的位元組碼;
本地變量表
每個本地變數賦值的過程
這裡我們可以得出一個結論:
Integer c =8;就是Integer c = Integer.valueOf(8);
上面Integer b = Integer.valueOf(8);,那就說明變數b和c都是使用Integer.valueOf()獲取到的。
valueOf方法中
-XX:AutoBoxCacheMax不設定
上面關於IntegerCache的low和high已經進行了說明,low永遠是-128,所以當我們沒有設定
-XX:AutoBoxCacheMax 的值的時候,這時候 high=127。
當Integer.valueOf(8);的時候,就會進入上面程式碼中的if中。然後從IntegerCache中的陣列cache中獲取。
但是IntegerCache中的cache陣列是個常量陣列。
言外之意,就是一旦給這個陣列cache賦值後,就不會變了。
Integer b = Integer.valueOf(8);和Integer c=8;
b和c不就是都指向同一個引用地址嗎?
所以 b==c為true;
但是Integer b=Integer.valueOf(128);
此時就不進入if中,而是直接new一個Integer物件
所以此時
Integer b=Innteger.valueOf(128) ;和Integer c = 128;
都會各自new 一個Integer物件,
此時的b==c為false。
這裡也就是網上很多文章也就說到這裡,就是比較當前int i 這個i是不是在-128到127範圍之內。
-XX:AutoBoxCacheMax設定為130
如果我們把-XX:AutoBoxCacheMax設定為130。那麼上面
Integer b=Innteger.valueOf(128) ;和Integer c = 128;
也會進入if條件中。
最後b==c為true。
如何避坑
Integer是基本型別int的封裝類,那麼在平常使用的時候需要注意幾點:
1,如果使用Integer,注意Integer的預設是null,容易引起空指標異常NullPointerException。
2,如果使用int型別,注意int型別的初始值是0,很多設計某某狀態時,很喜歡用0作為某個狀態,這裡要小心使用。
3,另外從記憶體使用層面來講,int是基本資料型別,只佔用4個位元組,Integer是一個物件,當表示一個值時Integer佔用的記憶體空間要高於int型別,從節省記憶體空間考慮,建議使用int型別(建議瞭解一下Java物件記憶體佈局)。
4,Integer使用的時候,直接賦值,Integer c = 8,不要new Integer(8)。因為直接賦值就是Integer.valueOf方法使用快取,沒必要每次都new一個新物件,有效提高記憶體使用。
5,如果系統中大量重複的使用比127大的數,建議JVM啟動的時候為-XX:AutoBoxCacheMax=size 適當的大小,提升記憶體使用效率(但是也不能太大,上面我們已經演示了可能出現OOM)。
面試題
面試題1
Integer num1 = new Integer(10);
Integer num2 = new Integer(10);
System.out.println(num1.equals(num2));
System.out.println(num1 == num2);
面試題2
Integer num3 = 100;
Integer num4 = 100;
System.out.println(num3.equals(num4));
System.out.println(num3 == num4);
面試題3
Integer num5 = 1000;
Integer num6 = 1000;
System.out.println(num5.equals(num6));
System.out.println(num5 == num6);
把上面看完了,在回頭來看看這種面試題,還難嗎?
如果在面試中遇到面試題3,可以適當反問一下面試官是否有對快取範圍進行調整,或許某些面試官都會懵逼。
赤裸裸的吊打面試官。
總結
1.引用型別的==是比較對應的引用地址。
2.Integer中使用的預設快取是-128到127。但是可以在JVM啟動的時候進行設定。
3.Integer b=Integer.valueOf(8);和Integer b=8;是一樣的效果。
4.Integer.valueOf()方式,比較是否在快取範圍之內,在就直接從快取中獲取,不在new一個Integer物件。
5.每次使用new來建立Integer物件,是用不到IntegerCache快取的。
6.Integer中的使用快取的方式也可以理解為享元模式。