IntegerCache緩存佔用堆、棧、常量池的問題,自動拆裝箱的基本概念,Integer==int時的問題說明

原創聲明:做者:Arnold.zhao 博客園地址:https://www.cnblogs.com/zh94java

先普及一個基本概念:Java中基本數據類型的裝箱和拆箱操做

自動裝箱

在JDK5之後,咱們能夠直接使用Integer num = 2;來進行值的定義,可是你有沒有考慮過?Integer是一個對象呀,爲何我能夠不實例化對象,就直接來進行Value的定義呢?數據庫

通常狀況下咱們在定義一個對象的時候,頂多賦值爲一個null 即空值;
好比:Person pserson = null;可是確定不能夠Person person =2;這樣操做吧,
那爲何Integer,Float,Double,等基本數據類型的包裝類是能夠直接定義值的呢?數組

究其緣由無非是編譯器在編譯代碼的時候,從新進行了一次實例化的操做而已啦:
好比當咱們使用Integer num = 2 的時候,在JVM運行前的編譯階段,此時該Integer num = 2 將會被編譯爲
Integer num = new Integer(2); 那麼此時編譯後的這樣一個語法 new Integer(2) 則是符合JDK運行時的規則的,而這種操做就是所謂的裝箱操做;緩存

注意:(不要拿Integer和int類型來進行對比,int,float,這些是JDK自定義的關鍵字,
自己在編譯的時候就會被特殊處理,而Integer,Float,Double等則是標準的對象,對象的實現自己就是要有new 的操做纔是合理;
因此對於這些基本類型的包裝類在進行 Integer num = 2的賦值時,則的確是必需要有一個裝箱的操做將其變成對象實例化的方式這樣也纔是一個標準的過程;)數據結構

自動拆箱

那麼當你瞭解了對應的裝箱操做後,再來了解一下對應拆箱的操做:線程

當咱們把一個本來的Integer num1 = 2; 來轉換爲 int num1 = 2的時候實際上就是一個拆箱的操做,及把包裝類型轉換爲基本數據類型時即是所謂的拆箱操做;
通常當咱們進行對比的時候,編譯器便會優先把包裝類進行自動拆箱:如Integer num1 = 2 和 int num2 = 2;當咱們進行對比時
if(num1 == num2) 那麼此時編譯器便會自動的將包裝類的num1自動拆箱爲int類型進行對比等操做;code

裝箱及拆箱時的真正步驟

上述已經說過了自動裝箱時,其實是把 Integer num =2 編譯時變動爲了 Integer num = new Integer(2);
但實際上JDK真的就只是這麼簡單的進行了一下new的操做嗎?固然不是,在自動裝箱的過程當中其實是調用的Integer的valueOf(int i)的方法,來進行的裝箱的操做;
咱們來看一下這個方法的具體實現:我會直接在下述源碼中加註釋,直接看註釋便可orm

public static Integer valueOf(int i) {
            //在調用valueOf進行自動裝箱時,會先進行一次所傳入值的判斷,當i的值大於等於IntegerCache.low 以及 小於等於IntegerCache.high時,則直接從已有的IntegerCache.cache中取出當前元素return便可;
            if (i >= IntegerCache.low && i <= IntegerCache.high){
                            return IntegerCache.cache[i + (-IntegerCache.low)];
            }
            //不然則直接new Integer(i) 實例化一個新的Integer對象並return出去;
            return new Integer(i);
    }
    //此時咱們再看一下上述的IntegerCache究竟是作的什麼操做,以下類:(注意:此處IntegerCache是 private 內部靜態類,因此咱們定義的外部類是沒法直接使用的,此處看源碼便可)
    
    private static class IntegerCache {
            //定義一個low最低值 及 -128;
            static final int low = -128;
            //定義一個最大值(最大值的初始化詳情看static代碼塊)
            static final int high;
            //定義一個Integer數組,數組中存儲的都是 new Integer()的數據;(數組的初始化詳情看static代碼塊)
            static final Integer cache[];
    
            static {
                //此處定義一個默認的值爲127;
                int h = 127;
                //sun.misc.VM.getSavedProperty() 表示從JVM參數中去讀取這個"java.lang.Integer.IntegerCache.high"的配置,並賦值給integerCacheHighPropValue變量
                String integerCacheHighPropValue = sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
                //當從JVM中所取出來的這個java.lang.Integer.IntegerCache.high值不爲空時
                if (integerCacheHighPropValue != null) {
                    try {
                        //此處將JVM所讀取出的integerCacheHighPropValue值進行parseInt的轉換並賦值給 int i;
                        int i = parseInt(integerCacheHighPropValue);
                        //Math.max()方法含義是,當i值大於等於127時,則輸出i值,不然則輸出 127;並賦值給 i;
                        i = Math.max(i, 127);
                        //Math.min()則表示,當 i值 小於等於 Integer.MAX_VALUE時,則輸出 i,不然輸出 Integer.MAX_VALUE,並賦值給 h
                        //此處使用:Integer.MAX_VALUE - (-low) -1 的緣由是因爲是從負數開始的,避免Integer最大值溢出,因此這樣寫的,此處能夠先不考慮
                        h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
                    } catch( NumberFormatException nfe) {
                        // If the property cannot be parsed into an int, ignore it.
                    }
                }
                //最後把所獲得的最終結果 h 賦值給咱們親愛的 high 屬性;
                high = h;
    
                //如下賦值當前cache數組的最大長度;
                cache = new Integer[(high - low) + 1];
                int j = low;
                //而後進行cache數組的初始化循環;
                for(int k = 0; k < cache.length; k++)
                    //注意:此處new Integer(j++);是先實例化的j,也就是負數-128,因此也纔會有上述的Integer.MAX_VALUE - (-low) -1)的操做,由於數組中存儲的是 -128 到 high 的全部實例化數據對象;
                    cache[k] = new Integer(j++);
    
                // range [-128, 127] must be interned (JLS7 5.1.7)
                assert IntegerCache.high >= 127;
            }
    
            private IntegerCache() {}
        }

朋友們,由上述的代碼咱們即可以知道,自動裝箱時:對象

一、high的值若是未經過JVM參數定義時則默認是127,當經過JVM參數進行定義後,則使用所定義的high值,前提是不超出(Integer.MAX_VALUE - (-low) -1)的長度便可,若是超出這個長度則默認即是:Integer.MAX_VALUE - (-low) -1;blog

二、默認狀況下會存儲一個 -128 到 high的 Integer cache[]數組,而且已經實例化了全部 -128 到high的Integer對象數據;

三、當使用valueOf(int i)來自動裝箱時,會先判斷一下當前所需裝箱的值是否(大於等於IntegerCache.low && 小於等於IntegerCache.high) 若是是,則直接從當前已經全局初始化好的cache數組中返回便可,若是不是則從新 new Integer();

而當Integer對象在自動拆箱時則是調用的Integer的intValue()方法,方法代碼以下:能夠看出是直接把最初的int類型的value值直接返回了出去,而且此時返回的只是基本數據類型!

private final int value;

    public int intValue() {
        return value;
    }

因此,朋友們,讓咱們帶着上述的答案,來看下咱們常在開發代碼時碰到的一些問題:(請接着向下看哦,由於最後還會再涉及到一些JVM的說明哦)

原創聲明:做者:Arnold.zhao 博客園地址:https://www.cnblogs.com/zh94

Integer於Int進行==比較時的代碼案例

public static void main(String[] args) {
        Integer num1 = 2000;
        int num2 = 2000;
        //會將Integer自動拆箱爲int比較,此處爲true;由於拆箱後即是 int於int比較,不涉及到內存比較的問題;
        System.out.println(num1 == num2);
        Integer num3 = new Integer(2000);
        Integer num4 = new Integer(2000);
        //此處爲false,由於 num3 是實例化的一個新對象對應的是一個新的內存地址,而num4也是新的內存地址;
        System.out.println(num3 == num4);
        Integer num5 = 100;
        Integer num6 = 100;
        //返回爲true,由於Integer num5 =100的定義方式,會被自動調用valueOf()進行裝箱;而valueOf()裝箱時是一個IntegerCache.high的判斷的,只要在這個區間,則直接return的是數組中的元素
        //而num5 =100 及返回的是數組中下標爲100的對象,而num6返回的也是數組中下標爲 100的對象,因此兩個對象是相同的對象,此時進行 == 比較時,內存地址相同,因此爲true
        System.out.println(num5 == num6);
        Integer num7 = new Integer(100);
        Integer num8 = 100;
        //結果爲false;爲何呢?由於num7並非自動裝箱的結果,而是本身實例化了一個新的對象,那麼此時即是堆裏面新的內存地址,而num8儘管是自動裝箱,但返回的對象與num7的對象也不是一個內存地址哦;
        System.out.println(num7 == num8);
    }

原創聲明:做者:Arnold.zhao 博客園地址:https://www.cnblogs.com/zh94

總結

  • 一、因爲咱們在使用Integer和int進行比較時,存在着自動拆箱於裝箱的操做,因此在代碼中進行Integer的對比時儘量的使用 .equals()來進行對比;
    好比咱們定義以下一個方法:那麼咱們此時是沒法知曉num1 和num2的值是不是直接new出來的?仍是自動裝箱定義出來的?就算兩個值都是自動裝箱定義出來的,那麼num1 和num2的值是否超出了默認的-128到127的cache數組緩存呢?若是超出了那麼仍是new 的Integer(),此時咱們進行 == 對比時,無疑是風險最大的,因此最好的仍是 .equals()進行對比;除非是拿一個Integer和一個int基本類型進行對比可使用
    ,由於此時不管Integer是新new實例化的仍是自動裝箱的,在對比時都會被自動拆箱爲 int基本數據類型進行對比;
public void test(Integer num1,Integer num2){
        //TODO
    }
  • 二、合理的在項目上線後,使用-XX:AutoBoxCacheMax=20000 參數來定義自動裝箱時的默認最大high值,能夠很好的避免基本數據類型包裝類被頻繁堆內建立的問題;什麼個意思呢,通常狀況下咱們在項目開發過程當中,會大量使用Integer num = 23;等等的代碼,而且咱們在操做數據庫的時候,通常返回的Entity實體類裏面也會定義一大堆的Integer類型的屬性,而上述也提到過了,每次Integer的使用實際上都會被自動裝箱,對於超出-128和127的值,則會被建立新的堆對象;因此若是咱們有不少的大於127的數據值,那麼每次都須要在堆中建立臨時對象豈不是一個很惋惜的操做嗎,若是咱們在項目啓動時設置-XX:AutoBoxCacheMax=20000,那麼對於咱們經常使用的Integer爲2W如下的數字,則直接從IntegerCache 數組中直接取就好了,徹底就不必再建立臨時的堆對象了嘛;這樣對於整個JVM的GC回收來講,多多少少也是一些易處呀,避免了大量的重複的Integer對象的建立佔用和回收的問題呢;不是嘛

  • 三、以前在本人仍是初初初級,初出茅廬程序猿的時候,就常常聽到有的人說,JVM中關於-128到127的cache緩存是存在常量池裏面的,有的人說當你在定義int類型時其實是存儲在棧裏面的,搞的我也是很尷尬呀;很難抉擇,
    那麼如今呢,就給出一個最終的本人總結後的答案,以下:

  • 首先咱們看了上述自動裝箱的源碼之後,能夠知道,初始化的緩存數據是定義在靜態屬性中的:static final Integer cache[]; 因此,答案是:咱們自動裝箱的cache數組緩存的確是定義在常量池中的;每次咱們自動裝箱時的數組判斷,的確是從常量池中拿的數據,
    廢話,由於是 static final 類型的呀,因此固然是常量池中存儲的cache數組啦

  • 可是:關於int類型中定義的變量其實是存儲於棧空間的,這個也是沒錯的,由於關於JVM棧中有一個定義是:針對局部變量中的基本類型的字面量則是存儲在線程棧中的;(棧是線程的一個數據結構),
    因此對於咱們在方法中定義的局部變量:int a = 3 時,則的確是存儲在線程棧中的;而咱們在方法中定義局部變量 Integer a=300時,這個確定是在堆或者常量池中啦(看是否自動裝箱後使用常量池中cache);

  • 而對於咱們在類中定義的成員屬性來講,好比:static int a =3;此時則是在常量池中(無外乎什麼類型由於他是靜態的,因此常量池)而類的成員屬性 int a=3(則是在堆中,無外乎什麼屬性,普通變量所對應的對象內存都是堆中)

相關文章
相關標籤/搜索