java整型缓存

来源:互联网 发布:golang select 编辑:程序博客网 时间:2024/05/16 18:31

缓存大家应该都听说过,像计算机中的缓存用于提高计算机性能,浏览器的缓存会在下一次访问该网站时一定程度上提高访问速度。通常缓存是用空间换时间,那么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

IntegerCacheInteger类的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=<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() {}}

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字面值truefalse,或者是在'\u0000''\u007f'之间的字符字面值。那么,对于p经过两次装箱操作形成的两个对象a和b,a==b总是成立的

以上语句确保值在-128到127之间的对象引用是相同的.同时还可以根据这句话得到这些信息:

理论上,我们可以缓存所有的原始类型值。但实际上,使用现有的技术是不可能实现的。上边的规则是一个比较现实的实现,要求一定范围的原始类型类装箱后总是获得相同的引用。规则没有向程序员保证其他值会的得到相同或者不同的的引用。这允许(而不是要求)共享一些或者全部其他基本类型值的引用。注意长整类型的字面量可以共享引用,当然这不是必须的 。
这会保证在大多数情况下,行为总是可靠、可预测的,同时没有太多的性能损失,特别是对于运行在小型设备上的程序。更少的内存的使用是可能实现的,比如:缓存所有的charshort类型的值,以及在-32K到32K的范围内的intlong类型的值。

其他缓存对象

缓存行为并不仅仅适用于Integer对象,在所有的整型类型类上都有相似的缓存机制。
- ByteCache用于缓存Byte对象
- ShortCache用于缓存Short对象
- LongCache用于缓存Long对象
- CharacterCache用于缓存Character对象
ByteShortLong有固定的缓存范围,比如在-128和127(包含)之间的值。对于Character类,缓存的范围为0到127(包含),除了Integer外,其他类型的缓存范围不能通过启动参数修改。

翻译于2015/04/13

0 0