Java中Integer等包装类型的cache机制

0.问题引出

先看一段测试代码,猜一下输出会是什么:

public class Test {
    public static void main(String[] args){
        Integer i1 = 127;
        Integer i2 = 127;
        Integer i3 = Integer.valueOf(127);      
        Integer i4 = new Integer(127);      
        Integer i5 = 128;
        Integer i6 = 128;

        System.out.println(i1 == i2);
        System.out.println(i1 == i3);
        System.out.println(i1 == i4);
        System.out.println(i5 == i6);
    }
}











不许偷看…










答案揭晓:

true
true
false
false

是不是有点迷糊了,i1、i2虽然包装的值相同,但是明明是两个对象,为什么会输出true。还有i3和i1为什么也输出true。i1、i4,i5、i6包装的值也相同啊,为什么就变成false了啊。这些奇怪的输出都是cache机制惹的祸,别着急,待我们细细分解。

1.cache机制到底是什么意思

从Java 5开始,为了节省内存提升Integer类性能,增加了cache机制。取值在一定范围(默认为[-128~127],咦,我为什么要说默认?)内的Integer对象会被缓存起来,以后这些对象就可以重复使用。cache机制只有在自动装箱(autoboxing)、以及手动装箱(手动是我自己的说法,即调用Integer.valueOf(int i)方法)时才会起作用。
用上面这段话解释我们测试程序的输出,由自动装箱和手动装箱创建的值为127的Integer对象指向同一个对象,这就是为什么我们前两行输出会是true。而i4是直接创建的Integer对象,不会用到cache机制,i5、i6的值超出cache范围,所以后两行输出false。
好奇的你会问了,为什么要这样做。注意前面加黑的两个词,对,就是节省内存提升性能。人们频繁使用的数常常是那些取值比较小的,Java设计者基于这样的统计学理论,将[-128,127]之间的数缓存起来,以后使用的时候直接从缓存中取出对象索引,而不用再重新创建。哎,不是说节省内存和提升性能吗,开头的小程序只用到了6个Integer对象,你一下子在缓存里搞出来256个对象,骗子。当然了,得附加一句,只有在程序很大的时候,cache机制的优点才能体现出来。
写到这里,cache是什么我们已经清楚了,接下来更进一步,看一看cache机制是怎么实现的。

2.cache机制如何实现

在Integer类的源码中,我们看到了一个静态内部类IntegerCache。

    /**
     * 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() {}
    }

哈,也没有什么神奇的嘛,就是在第一次调用IntegerCache类的时候,把所有[low,high]范围内的对象一次性创建好,存到cache[]数组中。以后使用的时候,直接返回对象而不是新建。
前面说到,只有在自动装箱和手动装箱时才会用到cache机制,搜索源码也证实了这一说法,IntegerCache类只在valueOf方法调用时才用到。先看Integer.valueOf(int i)的源码:

    /**
     * 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);
    }

和我们预想的一样,方法参数如果在cache范围内,直接返回,否则new一个新对象。手动装箱解决了,那么自动装箱是怎么利用cache机制的呢。
用jdk自带的反编译工具javap对我们.class进行反编译。(javap是jdk自带的反编译工具,可以用javap -help查看指令帮助,这里我们用javap -c),进入Test.class文件所在目录,执行

javap -c Test

输出中我们找到了这样的内容:

……
public static void main(java.lang.String[]);
Code:
0: bipush 127
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
…….

原来自动装箱也是在字节码层面调用了Integer.valueOf()方法。
此外,在IntegerCache源码中,我们看到了这样一行

String integerCacheHighPropValue =
sun.misc.VM.getSavedProperty(“java.lang.Integer.IntegerCache.high”);

说明cache数组的上界high是可以改变的。利用jvm参数[-XX:AutoBoxCacheMax=size]可以改变上界。从命令行运行Test.class

java -XX:AutoBoxCacheMax=1000 Test

输出变为

true
true
false
true

3.其他

至此,Integer的cache机制算是说清楚了。应该注意,除了Integer,还有以下几种包装类也有cache机制:Byte、Short、Long、Character。不过有一点区别,这四类的cache[]数组长度都是固定的,不可改变。且Byte、Short、Long的cache范围相同,[-128,127],Character则为[0,128]。Boolean取值只有2个,valueOf使用两个常量来自动装箱。

猜你喜欢

转载自blog.csdn.net/mr_cuber/article/details/73433598