最近面试被问到了Integer的缓冲池和Long的缓冲池,虽然面试回答对了,但是决定整理一下相关的知识。
在Java的内存分配中,总共3种常量池:
1.字符串常量池(String Constant Pool):
1.1:字符串常量池在Java内存区域的哪个位置?
1.2:字符串常量池是什么?
-XX:StringTableSize=66666
1.3:字符串常量池里放的是什么?
需要说明的是:字符串常量池中的字符串只存在一份!
如:
String s1 = "hello,world!";
String s2 = "hello,world!";
System.out.println(s1==s2);//true
即执行完第一行代码后,常量池中已存在 “hello,world!”,那么 s2不会在常量池中申请新的空间,而是直接把已存在的字符串内存地址返回给s2。
String a = "abc";
String b = new String("abc");
System.out.println(a == b);
----*----
结果:false
这个是第一个需要理解的地方,a指向哪片内存,b又指向哪片内存呢?对象储存在堆中,这个是不用质疑的,而a作为字面量一开始储存在了class文件中,之后运行期,转存至方法区中(JDK6之前)。它们两个就不是同一个地方存储的。知道了它之后我们就可以通过实例直接进一步了解了。
String s1 = "Hello";
String s3 = "Hel" + "lo";
System.out.println(s1 == s3); // true
这里要注意一下,因为做+号的时候,会进行优化,自动生成Hello赋值给s3,所以也是true 。
String s1 = "Hello";
String s4 = "Hel" + new String("lo");
System.out.println(s1 == s4); // false
S4的过程是创建了一个StringBuffer对象,然后用StringBuffer对象执行append方法追加,最后再转成String类型,也就是new String("lo")是放在heap里面的对象,s1是放在String常量池里的。两个的内存地址不一样。故结果为false。
String s1="abc";
String s3=new String("ab")+new String("c");
String s6 = s3.intern();
System.out.println(s1==s6); //true
intern用来返回常量池中的该字符串,如果常量池中已经存在该字符串,则直接返回常量池中该对象的引用。否则,在常量池中新建一个对象,然后 返回引用。
所处区域:堆
诞生时间:编译时
2.1:class常量池简介:
2.2:什么是字面量和符号引用:
诞生时间:JVM运行时
自动装箱常见的就是valueOf这个方法,自动拆箱就是intValue方法。在它们的源码中有一段神秘的代码值得我们好好看看。除了包装类Double Float没有实现这个缓存技术,其它的包装类均实现了它。
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
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 =
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的数全部被自动加入到了常量池里面,意味着这个段的数使用的常量值的地址都是一样的。一个简单的实例:
Integer i1 = 40;
Integer i2 = 40;
Double i3 = 40.0;
Double i4 = 40.0;
System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i3=i4 " + (i3 == i4));
-----结果----
true
false
原理如下:
1、== 这个运算在不出现算数运算符的情况下 不会自动拆箱,所以i1 和 i 2它们不是数值进行的比较,仍然是比较地址是否指向同一块内存
2、它们都在常量池中存储着,类似于这样
3、编译阶段已经将代码转变成了调用valueOf方法,使用的是常量池,如果超过了范围则创建新的对象
2: invokestatic #2 // Method java/lang/Integer.valueOf:(I)Ljava/lang/Integer;
Integer i1 = 40;
Integer i2 = 40;
Integer i3 = 0;
Integer i4 = new Integer(40);
Integer i5 = new Integer(40);
Integer i6 = new Integer(0);
System.out.println("i1=i2 " + (i1 == i2));
System.out.println("i1=i2+i3 " + (i1 == i2 + i3));
System.out.println("i1=i4 " + (i1 == i4));
System.out.println("i4=i5 " + (i4 == i5));
System.out.println("i4=i5+i6 " + (i4 == i5 + i6));
System.out.println("40=i5+i6 " + (40 == i5 + i6));
----结果----
(1)i1=i2 true
(2)i1=i2+i3 true
(3)i1=i4 false
(4)i4=i5 false
(5)i4=i5+i6 true
(6)40=i5+i6 true
它们的内存分布大概如下
注意点
1、当出现运算符的时候,Integer不可能直接用来运算,所以会进行一次拆箱成为基本数字进行比较
2、==这个符号,既可以比较普通基本类型,也可以比较内存地址看比较的是什么了
分析:
(1)号成立不用多说
(2)号成立是因为运算符自动拆箱
(3)(4)号是因为内存地址不同
(5)(6)号都是自动拆箱的结果
PS:equals方法比较的时候不会处理数据之间的转型,比如Double类型和Integer类型。
引申:
java中基本类型的包装类的大部分都实现了常量池技术,这些类是Byte,Short,Integer,Long,Character,Boolean,另外两种浮点数类型的包装类则没有实现。另外Byte,Short,Integer,Long,Character这5种整型的包装类也只是在对应值小于等于127时才可使用对象池,也即对象不负责创建和管理大于127的这些类的对象。以下是一些对应的测试代码:
public class Demo {
public static void main(String[]args) {
// 5种整形的包装类Byte,Short,Integer,Long,Character的对象,
// 在值小于127时可以使用常量池
Byte b1 = 127;
Byte b2 = 127;
System.out.println(b1==b2);// 输出true
Short s1= 127;
Short s2= 127;
System.out.println(s1==s2);// 输出true
// 值大于127时,不会从常量池中取对象
Short s3= 128;
Short s4= 128;
System.out.println(s3==s4);// 输出false
// Character
Character c1= 127;
Character c2= 127;
System.out.println(c1==c2);// 输出true
// 值大于127时,不会从常量池中取对象
Character c3= 128;
Character c4= 128;
System.out.println(c3==c4);// 输出false
// Boolean类也实现了常量池技术
Boolean bool1=true;
Boolean bool2=true;
System.out.println(bool1==bool2);// 输出true
// Long类也实现了常量池技术
Long l1 = Long.valueOf(100);
Long l2 = Long.valueOf(100);
Long l3 = 100l;
Long l4 = 100l;
System.out.println("l1=l2 " + (l1 == l2));//true
System.out.println("l3=l4 " + (l3 == l4));//true
// 浮点类型的包装类没有实现常量池技术
Double d1= 1.0;
Double d2= 1.0;
System.out.println(d1==d2);// 输出false
Float f1 = 1.0f;
Float f2 = 1.0f;
System.out.println(f1 == f2);// 输出false
}
}