聊一聊Integer的缓存机制问题
在Java编程中,Integer
类作为基本类型int的包装器,提供了对象化的操作和自动装箱与拆箱的功能。从JDK5
开始引入了一项特别的优化措施——Integer缓存机制,它对于提升程序性能和减少内存消耗具有重要意义。接下来我们由一段代码去打开Integer缓存机制的秘密。
public static void main(String[] args) {
Integer i1 = 100;
Integer i2 = 100;
System.out.println(i1 == i2);
Integer i3 = 1000;
Integer i4 = 1000;
System.out.println(i3 == i4);
}
至于答案是什么呢?我们接着往下看,等你看完就明白了。
当你在你的Idea中写出这段代码的时候,Idea就会提示你要使用
equals()
方法区比较大小,因为Integer
是对象,对象的值比较要用equals()
方法,而不是使用==
,这里我们主要是研究一下Integer
的缓存机制。
Integer缓存是什么
Java的Integer
类内部实现了一个静态缓存池,用于存储特定范围内的整数值对应的Integer
对象。默认情况下,这个范围是-128至127。当通过Integer.valueOf(int)
方法创建一个在这个范围内的整数对象时,并不会每次都生成新的对象实例,而是复用缓存中的现有对象。我们看一下Integer.valueOf(int)
的源码:
@HotSpotIntrinsicCandidate
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
对于Integer.valueOf(int)
方法来说,由于这个方法经常用于将基本类型int转换为包装器对象,所以它使用了@HotSpotIntrinsicCandidate
注解,这样HotSpot JVM可能会提供一种更为高效的内部实现来处理自动装箱操作。而IntegerCache
是Integer
内部的一个静态类,负责缓存整数对象。它在类加载时被初始化,创建并缓存范围内的所有整数对象。我们看一下IntegerCache
的源码:
private static class IntegerCache {
// 缓存范围的下限,默认为-128
static final int low = -128;
// 缓存范围的上限,初始化时动态计算(基于系统属性或默认值127)
static final int high;
// 存储在缓存范围内所有Integer对象的数组
static final Integer cache[];
// 静态初始化块,在类加载时执行
static {
// 初始设定high为127
int h = 127;
// 尝试从系统属性获取用户自定义的最大整数值
String integerCacheHighPropValue =
VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
// 如果系统属性存在并且可以转换为int类型,则更新high值
if (integerCacheHighPropValue != null) {
try {
int i = parseInt(integerCacheHighPropValue);
// 确保high至少为127,并且不超过Integer.MAX_VALUE允许的最大数组大小
h = Math.max(i, 127);
h = Math.min(i, Integer.MAX_VALUE - (-low) -1);
} catch( NumberFormatException nfe) {
}
}
// 设置最终确定的high值
high = h;
// 初始化cache数组,长度等于缓存范围内的整数数量
cache = new Integer[(high - low) + 1];
// 使用循环填充cache数组,创建并存储对应的Integer对象
int j = low;
for(int k = 0; k < cache.length; k++) {
cache[k] = new Integer(j++);
}
// 检查,确保缓存范围至少包含[-128, 127]
// 这是Java语言规范对小整数自动装箱共享的要求
assert IntegerCache.high >= 127;
}
// 私有构造器,防止外部实例化此内部类的对象
private IntegerCache() {}
}
IntegerCache
类在Java虚拟机启动时创建了一个固定大小的数组,用于缓存指定范围内所有的Integer
对象。这样在后续程序运行过程中,对于这些范围内的整数进行装箱操作时,可以直接从缓存中获取已存在的对象,以提升性能并减少内存开销。同时,它也提供了根据系统属性(-Djava.lang.Integer.IntegerCache.high
)来自定义缓存上限的能力,并确保满足Java语言规范关于小整数自动装箱共享的规定。
在Integer.value(int)
方法中,如果int
的值在IntegerCache
返回的low
和high
之内,则直接返回IntegerCache
中缓存的对象,否则重新new
一个新的Integer
对象。
而文章开头示例中,我们使用Interge i1 = 100
的方式其实是Java的自动装箱机制,整数字面量100
是一个基本类型的int值。当赋值给一个Integer
引用变量i
时,编译器会隐式地调用Integer.valueOf(int)
方法将这个基本类型的int值转换为Integer
对象。
整数在编程中经常被使用,特别是在循环计数等场景中,通过缓存整数对象,可以大幅度减少相同整数值的对象创建,从而减小内存占用。
由此我们可以看出因为100在[-128, 127]之内,所以i1 == i2
打印true
,而1000不在[-128, 127]之内,所以i3 == i4
打印false
。
我们尝试使用java.lang.Integer.IntegerCache.high
调整一下high
为1000,然后看一下效果:
打印结果都是true。
当然这个上限不要随意去调整,调整之前,需要仔细评估应用程序的实际需求和性能影响。尽量选择在[-128, 127]范围内的整数值,以充分利用Integer缓存机制。
注意事项
-
比较: 由于缓存的存在,在-128至127之间的
Integer
对象在进行==
运算符比较时,结果可能是true
,因为它们指向的是同一个内存地址。而在缓存范围之外创建的Integer
对象即使值相等,也会视为不同的对象,因此使用==
比较会返回false
。不论是否启用缓存,对于任何两个Integer
对象,只要其包含的整数值相同,调用equals()
方法始终会返回true
。所以我们在比较对象时一定要使用equals()
方法。 -
不适用于所有场景: 当使用
new Integer(i)
直接创建Integer
对象时,不会利用缓存。 -
不要随意去扩展缓存的上下限
总结
Integer缓存机制是Java中的一项性能优化措施,通过缓存一定范围内的整数对象,既能减小内存开销,又能提高性能。
本文已收录于我的个人博客:码农Academy的博客,专注分享Java技术干货,包括Java基础、Spring Boot、Spring Cloud、Mysql、Redis、Elasticsearch、中间件、架构设计、面试题、程序员攻略等