欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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

程序员文章站 2022-04-23 23:45:14
...

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使用两个常量来自动装箱。