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(则是在堆中,无外乎什么属性,普通变量所对应的对象内存都是堆中)

相关文章
相关标签/搜索