来道常见面试题
下面程序的输出结果是什么呢?
public class IntegerTest {
public static void main(String[] args) {
Integer a = 100, b = 100, c = 200, d = 200;
System.out.println(a == b);
System.out.println(c == d);
}
}
对的,可能大多数人都知道了,结果是true,false;原因就是Integer的缓存机制,会自动将-128~127缓存起来,使用时直接取就好。本篇文章带领大家具体分析一下。
分析源码
大家都知道Integer a =100;这个语句涉及到了自动装箱,会调用Integer.ValueOf()方法,将基本类型转化为对象类型;这个我们可以在该方法下打个断点就会知道。 那看一下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);
}
从注释可以看出,-128~127是IntegerCache的缓存范围,如果装箱的值在范围内,则直接从数组中IntegerCache返回,否则则调用构造方法创建新的对象。 既然这样,那我们再看一次啊IntegerCache这个数组是如何初始化赋值的:
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;为什么说是暂定,因为这个值是可以通过虚拟机的参数进行调整的,缓存的最大值是可以通过虚拟机参数 -XX:AutoBoxCacheMax=} 或 -Djava.lang.Integer.IntegerCache.high= 来设置的。默认是127罢了。
此时,我们反过头看答案梳理一下,正是因为装箱的值在-128~127之间,这些对象是直接从缓存的数组中获取,所以只要值相等,则获取的对象必定是同一对象,相反,则一定不是同一个对象。
分析反汇编
上面的方法有个地方稍显牵强,就是我们只能通过断点的方式判断自动装箱是调用ValueOf,下面从反汇编的角度分析一下,可以使用IDEA调用javap.exe,即可展示反汇编语句;当然也可直接在控制台中输出,先进行编译:javac IntegerTest .java,然后进行反汇编:javap -c IntegerTest 。可得到如下代码:
Compiled from "IntegerTest.java"
public class IntegerTest {
public IntegerTest();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."
4: return
public static void main(java.lang.String[]);
Code:
0: bipush 100
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
5: astore_1
6: bipush 100
8: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
11: astore_2
12: sipush 200
15: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
18: astore_3
19: sipush 200
22: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
25: astore 4
27: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
30: aload_1
31: aload_2
32: if_acmpne 39
35: iconst_1
36: goto 40
39: iconst_0
40: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
43: getstatic #3 // Field java/lang/System.out:Ljava/io/PrintStream;
46: aload_3
47: aload 4
49: if_acmpne 56
52: iconst_1
53: goto 57
56: iconst_0
57: invokevirtual #4 // Method java/io/PrintStream.println:(Z)V
60: return
}
Process finished with exit code 0
通过后面注释可以得知,在自动装箱是确实调用的是ValueOf的静态方法。因为我也是第一次接触反汇编代码,所以也就看懂个大概,里面的几个几个语法点可以说一下
bipush:当int取值-128~127时,JVM采用bipush指令将常量压入栈中。 sipush:当int取值-32768~32767时,JVM采用sipush指令将常量压入栈中。 iconst:当int取值-1~5时,JVM采用iconst指令将常量压入栈中 if_acmpeq: 比较栈两个引用类型数值,相等则跳转 if_acmpne: 比较栈两个引用类型数值,不相等则跳转 aload_n和astore_n是对应的,一个往里面放,一个从里面取
总结思考
缓存机制存在的原因:将频繁被使用的对象缓存起来,可以提升读取的效率,这是一个典型的用控件换时间的例子(其实缓存机制都是这个原理)Integer的缓存机制是这样,那其它类型比如Long,Short等应该也可以使用相同的分析方法进行分析反汇编方法是个不错的分析手段,多接触多了解(觉得自己写出来之后,理解更加深刻了)
参考:码出规范:《阿里巴巴Java开发手册》详解,慕课网明明如月老师的手记,推荐大家购买阅读,觉得很超值