1. 程式人生 > 實用技巧 >Integer快取範圍到底是多少?

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=} option.

註釋中已經說清楚,可以使用-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中的使用快取的方式也可以理解為享元模式。