Java中的常量池

1、全局字符串池(String Pool)

在JDK6.0及之前版本,字符串常量池是放在Perm Gen区(也就是方法区)中;在JDK7.0版本,字符串常量池被移到了堆中了。

全局字符串池中存放的是在类加载完成,经过验证,准备阶段之后在堆中生成字符串对象实例,然后将该字符串对象实例的引用值存到string pool中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的)。

在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是驻留字符串(也就是我们常说的用双引号括起来的)的引用(而不是驻留字符串实例本身),也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享

在JDK6.0中,StringTable的长度是固定的,长度就是1009,因此如果放入String Pool中的String非常多,就会造成hash冲突,导致链表过长,当调用String.intern()时会需要到链表上一个一个找,从而导致性能大幅度下降;在JDK7.0中,StringTable的长度可以通过参数指定:-XX:StringTableSize=66666

在JDK6.0及之前版本中,String Pool里放的都是字符串常量;在JDK7.0中,由于String.intern()发生了改变,因此String Pool中也可以存放放于堆内的字符串对象的引用。

注意:字符串常量池中的字符串只存在一份!比如String s1 = "hello"

String s1 = "hello";
String s2 = "hello";

执行完第一句之后,常量池中已存在“hello”,所以s2不会再在常量池中申请空间,而是直接把已存在的字符串的 内存地址 返回给s2。

2、class常量池(Class Constant Pool)

class文件中除了包含类的版本、字段、方法、接口等描述信息外,还有一项信息就是常量池(constant pool table),用于存放编译器生成的各种字面量(Literal)和符号引用(Symbolic References)。
字面量就是我们所说的常量概念,如文本字符串、八种基本类型的值、被声明为final的常量值等。
符号引用是一组符号来描述所引用的目标,符号可以是任何形式的字面量,只要使用时能无歧义地定位到目标即可(它与直接引用区分一下,直接引用一般是指向方法区的本地指针,相对偏移量或是一个能间接定位到目标的句柄)。一般包括下面三类常量:

  1. 类和接口的全限定名
  2. 字段的名称和描述符
  3. 方法的名称和描述符

常量池中每一项常量都是一个表,在JDK 1.7之前共有11种结构各不相同的表结构数据,在JDK 1.7中为了更好地支持动态语言调用,又额外增加了3种(CONSTANT_MethodHandle_info、CONSTANT_MethodType_info和CONSTANT_InvokeDynamic_info)。

这14种表都有一个共同的特点,就是表开始的第一位是一个u1类型的标志位(tag,取值见下表中标志列),代表当前这个常量属于哪种常量类型。这14种常量类型所代表的具体含义见下表

 

3、运行时常量池(Runtime Constant Pool)

jvm在执行某个类的时候,必须经过加载连接初始化,而连接又包括验证准备解析三个阶段。而当类加载到内存中后,jvm就会将class常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。 在上面也说了,class常量池中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析之后,也就是把符号引用替换为直接引用,解析的过程会去查询全局字符串池,也就是我们上面所说的StringTable,以保证运行时常量池所引用的字符串与全局字符串池中所引用的是一致的。

 

举例说明:

String str1 = "ab";
String str2 = new String("cd");
String str3 = "ab";
String str4 = str2.intern();
String str5 = "cd";
System.out.println(str1 == str3);//true
System.out.println(str2 == str4);//false
System.out.println(str4 == str5);//true

解释:

  1. 首先,在堆中会有一个”ab”实例,全局StringTable中存放着”ab”的一个引用值。
  2. 然后在运行第二句的时候会生成两个实例,一个是”cd”的实例对象,并且StringTable中存储一个”cd”的引用值,还有一个是new出来的一个”cd”的实例对象,与上面那个是不同的实例。
  3. 当在解析str3的时候查找StringTable,里面有”ab”的全局驻留字符串引用,所以str3的引用地址与之前的那个已存在的相同。
  4. str4是在运行的时候调用intern()函数,返回StringTable中”cd”的引用值,如果没有就将str2的引用值添加进去,在这里,StringTable中已经有了”cd”的引用值了,所以返回上面在new str2的时候添加到StringTable中的 “cd”引用值。
  5. 最后str5在解析的时候就也是指向存在于StringTable中的”cd”的引用值。

java中基本类型的包装类的大部分都实现了常量池技术,即Byte,Short,Integer,Long,Character,Boolean。这5种包装类默认创建了数值[-128,127]的相应类型的缓存数据,但是超出此范围仍然会去创建新的对象。 两种浮点数类型的包装类Float,Double并没有实现常量池技术。

 

举例说明:


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);//true
System.out.println(i1 == i2 + i3);//true
System.out.println(i1 == i4);//false
System.out.println(i4 == i5);//false
System.out.println(i4 == i5 + i6);//true
System.out.println(40 == i5 + i6);//true

// 浮点包装器不放入常量池
Float f1 = 1.0f;
Float f2 = 1.0f;
System.out.println(f1 == f2);//false

// 超过127,所以不会放入常量池
Integer i11 = 128;
Integer i12 = 128;
System.out.println(i11 == i12);//false



解释

  1. Integer i1=40;Java在编译的时候会直接将代码封装成Integer i1=Integer.valueOf(40);,从而使用常量池中的对象。
  2. Integer i1 = new Integer(40);这种情况下会创建新的对象。
  3. 语句i4 == i5 + i6,因为+这个操作符不适用于Integer对象,首先i5和i6进行自动拆箱操作,进行数值相加,即i4 == 40。然后Integer对象无法与数值进行直接比较,所以i4自动拆箱转为int值40,最终这条语句转为40 == 40进行数值比较。
  4. Float f1 = 1.0f,因为Float,Double并没有实现常量池技术,所以不会放入常量池,会创建一个全新的对象,Float f2同样道理,f1和f2是两个不同的对象。
  5. Integer i11 = 128,超出了Integer包装器的数值范围,因此会创建新对象,Integer i12 = 128语句同样如此,两个是不同对象。

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值