class文件中的常量池和运行时常量池:
Class常量池可以理解为class的资源仓库,里面记录着类的基本元数据,一般被叫做字面量和符号引用;class文件不光包含类的版本、字段、方法、接口、具体的代码操作之外,还包含常量池,用于存放编译期生成的字面量和符号引用;
我们一般不会人工解析16进制的字节码文件,会通过javap命令编译成可以阅读的字节码指令文件
javap -v CUPFindTest.class:
字面量是指由字母、数字等构成的字符串或数值常量;
字面量只能是属性中等号右边的值,比如int a=1;那么1就是字面量;包括文本字符串、被final声明的常量值和基本数据类型的值等;
符号引用是以一组符号来描述所引用的目标,符号可以是任何形式的字面量,只需要使用时无歧义的定位到目标即可,比如一个java类在编译时期并不知道引用类的实际地址,只能使用符号来代替,在运行或者加载期间,可以通过符号引用直接定位到实际对象的地址,把符号引用转成直接引用,这就是所谓的符号引用在运行或者加载阶段会转成符号引用代表对象的具体地址也就是直接引用,这就是所谓的动态链接;
符号引用包含:类和接口的全限定名、字段的名称和描述符、方法的名称和描述符;
class常量池在被加载到jvm内存后就会变成运行时常量池,在jdk1.7之后存在元空间之中。
字符串常量池:
设计实现:字符串的创建与销毁与其它对象一样,是消耗服务器性能的,作为基础的数据对象,大量频繁的创建字符串是浪费性能的。
所以为了解决以上的缺点设计了字符串常量池;
为字符串开辟了字符串常量池,类似于缓存区;
创建字符串时先查询缓存池是否存在该字符串,如果存在则直接返回,不存在则先创建该字符串放到池中,然后再返回。
String s = new String("abc");
String s1=s.intern();
System.out.println(s==s1);//false
这个会产生2个对象:
abc字符串对象会存到常量池中其实也是存在堆内存中,new也会产生一个对象存在堆内存中;
String的intern()方法会先到池中用String的equals(Object anObject)方法查找abc字符串,如果找到就返回,如果找不到就直接返回new出来对象的索引。
String s1 = new String("he") + new String("llo");
String s2 = s1.intern();
System.out.println(s1 == s2);//true
字符串常量池中会创建在he 和llo字符串,然后会分别new出这两个对象
两个字符串拼接在一起会在池中创建hello字符串,s1就是赋予此字符串的索引,然后说s2也会中池中找到hello字符串并返回其索引;所以上面s1和s2都是常量池中的索引;一共创建了5个对象。
String s0 = "bijian";
String s1 = new String("bijian");
String s2 = "bi" + new String("jian");
System.out.println(s0 == s1); // false
System.out.println(s0 == s2);//false
System.out.println(s1 == s2); // false
s0是字符串常量池中的对象在编译器已经放入到常量池中,是s1因为在编译器无法确定其地址所以是运行时创建出来的新对象;s2因为后半部分在编译器无法确定其地址,也是运行时创建出来的新对象。
String s0 = "bijian";
String s1 = "bi" + "jian";
final String j = "jian";
String i="jian";
String s2 = "bi" + j;
String s3= "bi" + i;
System.out.println(s0 == s1); // true
System.out.println(s0 == s2); // true
System.out.println(s0 == s3); // false
对这种+ 的字符串常量,在编译时期已经优化成一个拼在一起的字符串了,s0和s1都是字符串常量池中的引用地址;
对final修饰的属性也是视为常量,在编译阶段也会拼接在一起;
s3只用在运行阶段才知道其地址,是创建出来的新对象。
String s = "a" + "b";//等价于String s = "ab";
String a = "a";
String b = "b";
String s1 = a + b;
System.out.println(s==s1);//false
s1是通过两个字符串变量 + 链接在一起的,通过jvm指令可知这个 + 相当于
StringBuilder builder = new StringBuilder();
String s1 = builder.append(a).append(b).toString();
而这个toString()方法是new 出一个新的对象。
@Override
public String toString() {
// Create a copy, don't share the array
return new String(value, 0, count);
}
八种基本类型的包装类和对象池:
其中Byte、Short、Intger、Long、Character、Boolean都实现了对象池;
另外Byte、Short、Intger、Long、Character这五种包装类型只有值小于127才使用对象池,因为一般比较小的数用到的概率比较大;
Integer i1 = 127; //Integer.valueOf
Integer i2 = 127;
System.out.println(i1 == i2);//true
Integer i1 = 128; //Integer.valueOf
Integer i2 = 128;
System.out.println(i1 == i2);//false
从字节码指令可知,Integer i1 = 127相当于Integer i1 = Integer.valueOf(127);
再看看Integer的valueOf方法
public static Integer valueOf(int i) {
if (i >= IntegerCache.low && i <= IntegerCache.high)
return IntegerCache.cache[i + (-IntegerCache.low)];
return new Integer(i);
}
使用了static final Integer cache[]缓存,这个缓存在静态代码块中把数据放入缓存中的
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;
}
其中int low = -128; int high=127;