聊聊Java Integer缓存池IntegerCache
目录
- 开篇引入经典案例
- IntegerCache介绍
- 字节码验证
- 结论
- 扩展
- 扩展结论
- 写在结尾
本文由浅入深,全篇难度并不高,可能有些位置对于部分同学是盲区。
着急的同学可以直接跳到结论位置查看结论。
字节码验证、扩展环节新手阅读可能有些许困难,可以记录后,等待未来再学习一段时间再看也可以。
引入经典案例
从一个经典的案例开始
public class IntegerCacheExample {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
System.out.printf("a==b: %s.\n", a == b);
Integer c = 128;
Integer d = 128;
System.out.printf("c==d: %s.\n", c == d);
}
}
很简单两个输出,想个五秒钟后查看答案。
a==b: true.
c==d: false.
不知道跟你想的答案是 否一样,我们下面说一下为什么。
Integer缓存池 IntegerCache
Integer缓存池?是跟字符串缓存池类似的东西么?
缓存池的叫法是译文,跟字符串缓存池并不是一个意思,唯一相似的点就是池子里确实存储了一些值。
原文为IntegerCache,让我们去java.lang.Integer源码中一探究竟。
本文翻看的源码是JDK1.8.0-openjdk
大约780行左右找到 内部类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 -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 {
谷歌翻译解释如下:
缓存以支持 JLS 要求的 -128 和 127(含)之间值的自动装箱的对象标识语义。
缓存在第一次使用时初始化。 缓存的大小可以由 -XX:AutoBoxCacheMax= 选项控制。
在VM初始化过程中,java.lang.Integer.IntegerCache.high属性可能会被设置并保存在sun.misc.VM类的私有系统属性中。
重点关键字:
- -128~127
- 大小可由-XX:AutoBoxCacheMax调整
可以得到解释缓存生成的范围是-128~127,参数可以启动之前配置JVM参数AutoBoxCacheMax进行修改。
由此我们知道了为什么a==b是true,而c==d是false,因为a和b都是取自缓存池,指向的是同一指针,而c和d不从缓存池中取得,指向的是各自的内存地址。(如果不太理解什么是new对象指向新的内存地址的话,可以参考博主内存篇,找不到的话就是,内存篇可能还没写完,尚未发布)
我们尝试来验证一下这个结论是否正确
IntegerCache字节码验证-此段新手阅读较难,可以简易阅读,不必深究,未来都会弄懂
先从指针地址上查看一下。运行如下代码
System.identityHashCode(Object x)方法可以返回对象的内存地址,不管该对象的类是否重写了hashCode()方法。
System.out.printf("a:%s.\n", System.identityHashCode(a));
System.out.printf("b:%s.\n", System.identityHashCode(b));
System.out.printf("c:%s.\n", System.identityHashCode(c));
System.out.printf("d:%s.\n", System.identityHashCode(d));
输出如下:
a:1118140819.
b:1118140819.
c:1975012498.
d:1808253012.
可以看到其中a,b 指向同一地址,c,d指向不同地址。接着我们从字节码的角度看一下JVM如何做到的。
为了减少字节码复杂度,我注释了对比部分的代码
public class IntegerCacheExample {
public static void main(String[] args) {
Integer a = 127;
Integer b = 127;
// System.out.printf("a==b: %s.\n", a == b);
Integer c = 128;
Integer d = 128;
// System.out.printf("c==d: %s.\n", c == d);
}
}
生成字节码,在main方法这个类的目录下命令行模式执行命令,会生成一个同名的.class文件
javac IntegerCacheExample.java
查看字节码
javap -c IntegerCacheExample.class
字节码如下:
Compiled from "IntegerCacheExample.java"
public class com.xh.basic.lang.integer.IntegerCacheExample {
public com.xh.basic.lang.integer.IntegerCacheExample();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."":()V
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 127
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 127
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: sipush 128
15: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: astore_3
19: sipush 128
22: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
25: astore 4
27: return
}
是不是看不懂,看不懂不要着急,我也不是全能看懂,我们来找一些关键位置解读即可。
public static void main下的是开始了我们的主要代码,上方部分可以自动忽略即可,我们只关注重点。
在main下的2、8、15、22行处我们能看到
// Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer
可以猜到JAVA在运行时进行了如下优化调整
Integer x = num;
// 优化为
Integer x = Integer.valueOf(num);
我们随着源码来到Integr.valueOf方法内
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
源代码告诉我们当执行valueOf时,先判断i是否在 [low, high]范围内,如果是范围内,则取自IntegerCache,不在范围的则进行new Integer(i)操作,所以我们可以得出结论。
结论
当Integer x = num初始化时,如果num在 -128~127范围内(默认范围),会从IntegerCache中获取值,此时会指向同一指针地址,范围外时会new Integer操作,指向不同指针。
扩展
我们看到IntegerCache注释中提到了-XX:AutoBoxCacheMax,我们可以试一下,看有没有效果,更改后是什么改变。
我们在JVM运行参数上加上 -XX:AutoBoxCacheMax=200
我是用的是IDEA,部分idea没有这个VM options,我们在界面位置依次选择
Run --> Eidt Configurations --> Modify options --> Add VM options
添加之后就能在界面位置看到了,可以添加运行参数
Apply后 运行代码 查看效果
AutoBoxCacheMax=200
sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high");
此方法是获取 VM参数中的AutoBoxCacheMax的值
public class IntegerCacheExample {
public static void main(String[] args) {
System.out.printf("high:%s.\n", sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"));
Integer a = 127;
Integer b = 127;
System.out.printf("a==b: %s.\n", a == b);
Integer c = 128;
Integer d = 128;
System.out.printf("c==d: %s.\n", c == d);
Integer e = 200;
Integer f = 200;
System.out.printf("e==f: %s.\n", e == f);
Integer g = 201;
Integer h = 201;
System.out.printf("g==h: %s.\n", g == h);
System.out.printf("a:%s.\n", System.identityHashCode(a));
System.out.printf("b:%s.\n", System.identityHashCode(b));
System.out.printf("c:%s.\n", System.identityHashCode(c));
System.out.printf("d:%s.\n", System.identityHashCode(d));
System.out.printf("e:%s.\n", System.identityHashCode(e));
System.out.printf("f:%s.\n", System.identityHashCode(f));
System.out.printf("g:%s.\n", System.identityHashCode(g));
System.out.printf("h:%s.\n", System.identityHashCode(h));
}
}
结果输出如下:
high:200.
a==b: true.
c==d: true.
e==f: true.
g==h: false.
a:1118140819.
b:1118140819.
c:1975012498.
d:1975012498.
e:1808253012.
f:1808253012.
g:589431969.
h:1252169911.
可以看出 200时取自IntegerCache, 201则是不同指针的new Integer了。配置生效
AutoBoxCacheMax=126
看命名能看出是Max值,那如果比127小呢,是否有效?我们测试一下。
public class IntegerCacheExampleMinValue {
public static void main(String[] args) {
System.out.printf("high:%s.\n", sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"));
Integer a = 127;
Integer b = 127;
System.out.printf("a==b: %s.\n", a == b);
Integer c = 128;
Integer d = 128;
System.out.printf("c==d: %s.\n", c == d);
Integer min126a = 126;
Integer min126b = 126;
System.out.printf("min126a==min126b: %s.\n", min126a == min126b);
System.out.printf("a:%s.\n", System.identityHashCode(a));
System.out.printf("b:%s.\n", System.identityHashCode(b));
System.out.printf("c:%s.\n", System.identityHashCode(c));
System.out.printf("d:%s.\n", System.identityHashCode(d));
System.out.printf("min126a:%s.\n", System.identityHashCode(min126a));
System.out.printf("min126b:%s.\n", System.identityHashCode(min126b));
}
}
输出如下:
high:126.
a==b: true.
c==d: false.
min126a==min126b: true.
a:697960108.
b:697960108.
c:943010986.
d:1807837413.
min126a:2066940133.
min126b:2066940133.
min126a==min126b没问题,但是为什么a==b?此时缓存池的值high不是126么?那127应该是new的啊?难道配置没生效,其实还是127?
其实不是我们设置的没生效,通过打印high的值,我们发现配置确实是生效了的,但没生效的原因是源码中做了判断。贴一段IntegerCache中为缓存池最大值赋值的方法。
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.
}
}
关键代码
i = Math.max(i, 127);
可以看出,它取vm参数AutoBoxCacheMax的值与127做判断,取最大值,也就是说,如果我们设置的AutoBoxCacheMax参数比127小,则不会生效。
到此我们的IntegerCahce篇就完结了,如果有不明白的小伙伴可以文末留言给我,我会一一回复的。
扩展结论
-XX:AutoBoxCacheMax设置可以修改缓存池的最大范围,但需要大于127才能生效,小于等于127时,依然取的是默认值127。
写在结尾
本人是程序猿大辉,一个仍在辛勤奋斗的程序猿,希望你我都能在未来的路上不断学习、不断成长。
同时不要丢失快乐,做一个快乐的程序猿。
希望本文可以给你带来收获。