深入剖析Integer的缓存机制

上一篇,我们对Long、Short、Byte三个类的构造方法、内部Cache类以及valueOf进行了分析。 看了文章的人,肯定心中有个疑惑,怎么没有对最为常用的Integer类进行分析? 实际上,正是由于Integer最为常用,JDK源码对其内部缓存实现类IntegerCachevalueOf方法做了进一步的优化。

测试代码

我们首先还是来看一段测试代码。

@Test
public void testInteger() {
    int primaryInt = 127;
    Integer int1 = Integer.valueOf(primaryInt);
    Integer int2 = 127;

    Assert.assertTrue(int1 == int2);

    int1 = new Integer(127);
    Assert.assertFalse(int1 == int2);

    primaryInt = 128;
    int1 = Integer.valueOf(primaryInt);
    int2 = 128;

    Assert.assertFalse(int1 == int2); // 1

    int1 = new Integer(128);
    Assert.assertFalse(int1 == int2);
}
复制代码

上述测试代码在不设置JVM参数的情况下,是可以通过测试的。
但是一旦我们在运行该测试的时候设置了JVM参数-Djava.lang.Integer.IntegerCache.high=255,这个测试将在 注析1处失败!下图为IntelliJ IDEA中JVM参数的设置。

integer_jvm_cache_high.png

这又是为什么呢?
按照我们上一篇的分析,Integer类提供的缓存机制应该也是缓存[-128,127]之间的值。 在不提供-Djava.lang.Integer.IntegerCache.high=255JVM参数的时候确实是对的。但是一旦提供了该参数,情况就发生了变化。 变化究竟是怎样的?我们且看Integer的源代码。

分析

我们打开java.lang.Integer的源代码。其中关键性代码如下。

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 =
         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() {}
}
复制代码

上面是Integer的内部缓存类IntegerCache的源代码,我们发现跟Long#LongCache的代码要复杂不少。

  • IntegerCache内部还是一样使用的cache数组作为缓存数据的机制;
  • IntegerCache使用了lowhigh分别作为缓存的数值的最小值和最大值;
  • low实际上是一个固定的值-128
  • high是可以设置的:
    1. 通过设置变量h=127作为high的默认值;
    2. 通过VM.getSavedProperty("java.lang.Integer.IntegerCache.high")获取java.lang.Integer.IntegerCache.high参数值视为变量integerCacheHighPropValue,并将 该值转换为int,然后取127和该转换后的int值中较大的作为变量i的值;
    3. i, Integer.MAX_VALUE - (-low) -1中较小的值作为h的值;这里之所以取中较小的值,是因为数组size最大为Integer.MAX_VALUE。 也就是说数组元素下标最大为Integer.MAX_VALUE - 1的。而IntegerCachecache数组中需要存放128个负数,所以cache的下标需要再减128也就是-low, 也就是Integer.MAX_VALUE - (-low) -1
    4. h再赋值给high作为能够缓存的最大整数。
  • 计算出high之后的代码就是初始化cache数组的过程。上一篇已经分析过了,这里就不再重复了。

从上述计算high的过程中可以看出,java.lang.Integer.IntegerCache.high参数的设置必须要大于127的整数,否则该设置就是无效的。

由于缓存机制的变化,Integer.valueOf的实现也发生一些变化,如下所示。 实际上,也就是只对IntegerCache.lowIntegerCache.high之间的值返回缓存。

public static Integer valueOf(int i) {
    if (i >= IntegerCache.low && i <= IntegerCache.high)
        return IntegerCache.cache[i + (-IntegerCache.low)];
    return new Integer(i);
}
复制代码

总结

  • 如果项目需要大量重复使用范围较大的Integer类型值的时候,请通过设置java.lang.Integer.IntegerCache.high参数来扩充缓存:
  • 通过命令行运行的话,可以这样设置:java -Djava.lang.Integer.IntegerCache.high=xxx Main.class
  • 通过特定开发工具运行的话,直接设置运行参数:-Djava.lang.Integer.IntegerCache.high=xxx
  • java.lang.Integer.IntegerCache.high参数的值应该是一个大于127的值,否则跟没有设置效果是一样的。

最后,这里为什么low是一个固定值-128? 按理,我们也是需要扩充负数的缓存范围的。至今也没有一个开发人员给出合理的解析。

猜你喜欢

转载自juejin.im/post/5e722127518825490b649ff5