缓存大家应该都听说过,像计算机中的缓存用于提高计算机性能,浏览器的缓存会在下一次访问该网站时一定程度上提高访问速度。通常缓存是用空间换时间,那么java中既节省了内存又提高了效率的缓存大家是否知道呢?这里就向大家介绍介绍”Java Integer Cache”。
这篇博客翻译自《Java Integer Cache》,限于本人英文水平,翻译可能有些偏差,请大家见谅
这篇Java文章是介绍整型缓存的。这个特性是在Java 5时为了节省内存、提高效率而引入Java的。让我们先看一段简单的使用了Integer并且展示了整型缓存行为的代码。从这里,我们可以学习整型缓存是如何实现的,以及为什么这样实现。你可以猜测一下下面这段Java程序的输出结果。显然这里的输出有些问题,这也是我写这篇博客的原因。
Integer integer1 = 3;
Integer integer2 = 3;
if (integer1 == integer2)
System.out.println("integer1 == integer2");
else
System.out.println("integer1 != integer2");
Integer integer3 = 300;
Integer integer4 = 300;
if (integer3 == integer4)
System.out.println("integer3 == integer4");
else
System.out.println("integer3 != integer4");
通常,我们会认为这两个表达式都会返回false。虽然比较的两个值是一样的,但是比较的两个对象却应该是两个不同引用。如果你是初学者,那么请注意,在Java中==比较对象的引用,而equals()通常是比较对象的值。所以在这个例子中,比较的两个对象有不同的引用,他们应该返回false。奇怪的是,他们的结果并不相同,两个相似的if表达式返回了不同的boolean值.
让我们看看上面那段Java程序的输出
integer1 == integer2
integer3 != integer4
Java整型缓存实现
在Java 5中,一个新特性被引入为了节省内存和改善整数类型的对象的处理性能。整数对象会在内部缓存并通过相同的引用对象重用
这个特性被用于在-128到+127(包含)之间的Integer对象
Integer缓存只有在自动装箱时才会使用。如果一个Integer对象是通过构造函数构建的,那么将不会使用已经缓存的引用。
由Java编译器自动将原始数据类型转换为相应的Java包装类的操称为自动装箱。这相当于调用valueOf方式:
Integer a = 10; // 自动装箱
Integer b = Integer.valueOf(10); // 效果同自动装箱
所以现在我们知道这个缓存应该是在Java类库源代码中实现的。让我们看看Java类库中的valueOf方法的源码(JAVA_HOME/src.zip解压)。以下的代码是Java JDK 1.8.0 build 40中的。
/** * 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);
}
在构造一个Integer实例之前,首先在IntegerCache.cache中查表寻找。IntegerCache是一个用于维护Integer类型缓存的类。
IntegerCache 类
IntegerCache是Integer类的private static内联类。让我们看看这个类的源码。里面有相当好的文档注释,这会给我们很多信息。
/** * 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=} 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() {}
}
JavaDoc注释清楚的说明:这个类是为了缓存并且支持数值在-128到127之间的整型变量的自动装箱操作。最大值127可以使用VM的启动参数-XX:AutoBoxCacheMax=size修改。所以,缓存的内容是在一个for循环中创建的。它从最小值循环到最大值,并且创建对应Integer变量,之后存储在一个名为cache的Integer数组中。这些缓存在第一次使用Integer类时创建。之后,这些被缓存起来的实例会在自动装箱时用来代替创建新的实例。
实际上,在Java 5引入这个特性时,缓存的范围是固定的:-128到127。在Java 6以后,这个范围的最大值被映射到了java.lang.Integer.IntegerCache.high属性上,并且允许我们通过VM参数设置最大值。这允许我们根据使用的情况灵活调整性能。那么选择缓存的数字范围为-128到127的原因是什么呢?这是因为,这个范围被认为是整数使用最频繁的范围。当然,在程序中第一次使用Integer类时,需要额外的时间缓存这些实例。
Java语言规范中的Cache规定
在Java Language Specification(JLS)的装箱转换部分有如下声明:
如果p是在-128到127(包含)之间的整型字面值,或者boolean字面值true、false,或者是在'\u0000'到'\u007f'之间的字符字面值。那么,对于p经过两次装箱操作形成的两个对象a和b,a==b总是成立的
以上语句确保值在-128到127之间的对象引用是相同的.同时还可以根据这句话得到这些信息:
理论上,我们可以缓存所有的原始类型值。但实际上,使用现有的技术是不可能实现的。上边的规则是一个比较现实的实现,要求一定范围的原始类型类装箱后总是获得相同的引用。规则没有向程序员保证其他值会的得到相同或者不同的的引用。这允许(而不是要求)共享一些或者全部其他基本类型值的引用。注意长整类型的字面量可以共享引用,当然这不是必须的 。
这会保证在大多数情况下,行为总是可靠、可预测的,同时没有太多的性能损失,特别是对于运行在小型设备上的程序。更少的内存的使用是可能实现的,比如:缓存所有的char和short类型的值,以及在-32K到32K的范围内的int和long类型的值。
其他缓存对象
缓存行为并不仅仅适用于Integer对象,在所有的整型类型类上都有相似的缓存机制。
- ByteCache用于缓存Byte对象
- ShortCache用于缓存Short对象
- LongCache用于缓存Long对象
- CharacterCache用于缓存Character对象
Byte、Short、Long有固定的缓存范围,比如在-128和127(包含)之间的值。对于Character类,缓存的范围为0到127(包含),除了Integer外,其他类型的缓存范围不能通过启动参数修改。
翻译于2015/04/13