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使用两个常量来自动装箱。
推荐阅读
-
Java中Integer和int数据类型的比较
-
Java中包装类与基本类型运算的性能比较(Integer i += 1)
-
详解Java中包装类Integer的使用
-
Java中基本类型和包装类型的区别(装箱、拆箱)
-
java.lang.integer PHP中的integer类型使用分析
-
Java中Integer等包装类型的cache机制
-
Java:如何利用反射将String类型的集合中添加整形,布尔型等类型(泛型知识)
-
Java中 int Integer 和 String类型的转换
-
Java中Integer和int数据类型的比较
-
java.lang.integer PHP中的integer类型使用分析