暂无图片
暂无图片
暂无图片
暂无图片
暂无图片

面试踩过的坑:Java Integer 缓存

ITSK 2020-01-23
925

上善若水,厚德载物

                                —— 老子






今天我们从一个简单问题入手介绍一下Java的Integer缓存机制。大家先想一想下面代码的结果是什么呢?

Integer  num = Integer.valueOf(12);
System.out.println(num);


Integer num1 = 88;
Integer num2 = 88;
System.out.println("num1 == num2:"+(num1 == num2));


Integer num3 = 880;
Integer num4 = 880;
System.out.println("num3 == num4:"+(num3 == num4));


Integer num5 = new Integer("88");
Integer num6 = new Integer("88");
System.out.println("num5 == num6:"+(num5 == num6));
System.out.println("num2 == num5:"+(num2 == num5));
有小伙伴可能理所当然心想,很简单呀false、false呗,有理有据因为在Java中“==”号比较的是两个对象的地址,很显然num1和num2、num3和num4是不同的对象,所以是false。我第一此也是这样想的,嘿main方法run一下,不妙结果如下:
num1 == num2:true
num3 == num4:false

和预想的不一样呢,其本质就是因为存在Java Integer缓存机制,然后看源码详细梳理了一下,分享给小伙伴们

Java Integer 缓存机制

在Java5中,为Integer的操作引入了一个新的特性,用来节省内存和提高性能。整型对象在内部实现中通过使用相同的对象引用实现了缓存和重用。

其原理是引入了IntegerCache,默认会把一些常用的小的数字缓存起来(整数区间 -128 到 +127),这样使用的时候不需要每次都去new新的对象。这种 Integer 缓存机制仅在自动装箱的时候有用,使用构造器创建的 Integer 对象不能被缓存。我们一会儿通过源码可以看到。

自动装箱:装箱就是自动将基本数据类型转换为包装器类型,默认调用valueOf()方法;

自动拆箱:装箱就是自动将基本数据类型转换为包装器类型,默认调用intValue()方法;

所以 Integer num1 = 88; 实质相当于Integer num1 = Integer.valueOf("88"); ,我们看valueOf方法源码如下:

 /**
* 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);
}


私有静态内部类: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() {}
}

这个类是用来实现缓存支持,并支持-128到127之间的自动装箱过程。最大值 127 可以通过 JVM 的启动参数 -XX:AutoBoxCacheMax=num 修改,如下图所示:

 此时再运行上面代码如下:

Integer num1 = 88;
Integer num2 = 88;
System.out.println("num1 == num2:"+(num1 == num2));


Integer num3 = 880;
Integer num4 = 880;
System.out.println("num3 == num4:"+(num3 == num4));


运行结果:
num1 == num2:true
num3 == num4:true

如上修改Integer 最大缓存值为1000时,num3和num4也相等啦。

我们可以看到缓存实现是通过一个for循环,从小到大创建指定范围内的整数并存储在一个名为cache的整数数组中。这个缓存会在Integer类第一次被使用的时候被初始化出来。以后,就可以使用缓存中包含的实例对象,而不是创建一个新的实例(在自动装箱的情况下)。

在 Java5中引入这个特性的时候,范围是固定的-128至+127。后来在 Java6中,最大值映射到java.lang.Integer.IntegerCache.high,可以使用 JVM的启动参数设置最大值。这使我们可以根据应用程序的实际情况灵活地调整来提高性能。

为什么会选择-128到127这个范围呢?因为这个范围的整数值使用最广泛。在程序中第一次使用Integer的时候也需要一定的额外时间来初始化这个缓存。


其他缓存的对象

这种缓存行为不仅适用于Integer对象。我们针对所有整数类型的类都有类似的缓存机制。

ByteCache 用于缓存 Byte 对象(-128 ~ +127):

    private static class ByteCache {
private ByteCache(){}


static final Byte cache[] = new Byte[-(-128) + 127 + 1];


static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Byte((byte)(i - 128));
}
}

有 ShortCache 用于缓存 Short 对象(-128 ~ +127):

    private static class ShortCache {
private ShortCache(){}


static final Short cache[] = new Short[-(-128) + 127 + 1];


static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Short((short)(i - 128));
}
}

有 LongCache 用于缓存 Long 对象(-128 ~ +127):

private static class LongCache {
private LongCache(){}


static final Long cache[] = new Long[-(-128) + 127 + 1];


static {
for(int i = 0; i < cache.length; i++)
cache[i] = new Long(i - 128);
}
}

有 CharacterCache 用于缓存 Character 对象(0 ~ +127):

 private static class CharacterCache {
private CharacterCache(){}


static final Character cache[] = new Character[127 + 1];


static {
for (int i = 0; i < cache.length; i++)
cache[i] = new Character((char)i);
}
}

Byte,Short,Long 有固定范围: -128 到 127。对于 Character, 范围是 0 到 127。除了 Integer 可以通过参数改变范围外,其它的都不行。


或许有时候有些基础东西我们注意不到,但是某些机制和思想我们还是可以学习的。好啦,Java的Interger缓存机制就总结到这里,欢迎小伙伴们评论区交流哦




欢迎关注ITSK,每天进步一点点,我们追求在交流中收获成长和快乐

文章转载自ITSK,如果涉嫌侵权,请发送邮件至:contact@modb.pro进行举报,并提供相关证据,一经查实,墨天轮将立刻删除相关内容。

评论