聊聊Java Integer缓存池IntegerCache

目录

  1. 开篇引入经典案例
  2. IntegerCache介绍
  3. 字节码验证
  4. 论断
  5. 扩大
  6. 扩大论断
  7. 写在结尾

本文由浅入深,全篇难度并不高,可能有些地位对于局部同学是盲区。

焦急的同学能够间接跳到论断地位查看论断。

字节码验证、扩大环节老手浏览可能有些许艰难,能够记录后,期待将来再学习一段时间再看也能够。

引入经典案例

从一个经典的案例开始

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=<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 {

谷歌翻译解释如下:

缓存以反对 JLS 要求的 -128 和 127(含)之间值的主动装箱的对象标识语义。缓存在第一次应用时初始化。 缓存的大小能够由 -XX:AutoBoxCacheMax=<size> 选项管制。在VM初始化过程中,java.lang.Integer.IntegerCache.high属性可能会被设置并保留在sun.misc.VM类的公有零碎属性中。

重点关键字

  1. -128~127
  2. 大小可由-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."<init>":()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。

写在结尾

自己是程序猿大辉,一个仍在辛勤奋斗的程序猿,心愿你我都能在将来的路上一直学习、一直成长。

同时不要失落高兴,做一个高兴的程序猿。

心愿本文能够给你带来播种。