Java String实例的创建和常量池的关系及intern方法

Java String实例的创建和各种常量池的关系


常量池


class文件常量池(class constant pool)

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

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

这个常量池只是编译后的class的文件中的一部分,不是在程序运行的一部分。

运行时常量池(runtime constant pool)

jvm在加载某个类的时候,必须经过装载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将class文件常量池中的内容存放到运行时常量池中,由此可知,运行时常量池也是每个类都有一个。class文件常量池中的存放的是各种字面量和符号引用,解析阶段就是虚拟机将符号引用转换为直接引用的过程,符号引用就是一组符号来描述所引用的目标,符号可以是任何i形式的字面量,符号引用和虚拟机的内存布局没有联系,直接引用可以是内存中,直接指向目标的指针丶相对偏移量,或是一个能间接定位到目标的句柄,解析的这个阶段其实就是将符号引用转换为可以直接定位对象等在内存中的位置的直接引用。
运行时常量池是保存在JVM内存的方法区中的。

全局字符串常量池(string pool也有叫做string literal pool)

String类维护的一个常量池,在类加载完成,验证,准备阶段之后,在堆中生成字符串实例,然后将引用放到常量池中(记住:string pool中存的是引用值而不是具体的实例对象,具体的实例对象是在堆中开辟的一块空间存放的。)。 在HotSpot VM里实现的string pool功能的是一个StringTable类,它是一个哈希表,里面存的是key(字面量“abc”, 即驻留字符串)-value(字符串"abc"实例对象在堆中的引用)键值对,也就是说在堆中的某些字符串实例被这个StringTable引用之后就等同被赋予了”驻留字符串”的身份。这个StringTable在每个HotSpot VM的实例只有一份,被所有的类共享(享元模式)。在Java7的时候将这个常量池从方法区移动到堆中,因为之前在方法区(永久代)时,,受到方法区内存大小的限制,如果大量调用intern方法,会使永久代OOM。

下文中提到的常量池中的对象指这个常量池中的一个<k,v>键值对,key是字符串字面量,value是堆中实例的引用。
String s1 = "abc"

JVM会扫描class文件常量池出现的所有引号包裹的字面量,这里是“abc”,
首先看常量池中有没有这个"abc"对象,发现没有
然后在堆中创建这个String实例,之后将这个字面量“abc”作为key,这个实例引用作为value驻留到全局字符串常量池中,Stringtable是hashtable结构Stringtablesize大小默认是65535.

直接使用字面量的方式初始化String变量,首先会在常量池中找,找不到才会创建,然后将常量池中的这个对象的引用返回赋值给变量,如果使用new关键字创建实例,一定会创建一个新对象。

栗1

String s2 = new String("ab")+new String("c")

这个例子中,加载过程中,
首先看常量池中有没有"ab"和"c"这两个对象,发现没有
会在堆中先创建"ab"和"c"两个实例,然后将"ab"字面量和"ab"这个实例的引用都驻留到常量池中,将"c"字面量和"c"这个实例的引用也驻留到常量池中,
之后在运行阶段,初始化s2的时候,会在堆中创建字符串"abc"实例
如果这时调用intern的时候,发现常量池中没有这个"abc"对象,就把这个"abc"字面量和这个"abc"实例引用放到常量池中,然后返回这个引用。


在解析阶段,会将运行时常量池中的符号引用转换为直接引用,就是使运行时常量池中的引用和全局字符串常量池中引用的String实例的地址一致的过程。

栗2

	String s3 = new StringBuilder("ab").append("c").toString();
	System.out.println(s3.intern()==s3);
		
	String s4 = new StringBuilder("hello").toString();
	System.out.println(s4.intern()==s4);
	
//运行结果
	true
	false

这个例子中,加载过程中
先看常量池中有没有"ab",“c”,“hello"这三个对象,发现没有
就会在堆中创建这三个实例,然后将他们的字面量和实例引用放到常量池中
在运行阶段初始化s3的时候,首先会在堆中创建实例"abc”
之后调用intern方法,看这个对象是否在常量池中,发现不在,会将这个实例引用放在常量池中,返回这个实例的引用
然后打印就是同一个对象。
初始化s4的时候,首先会在堆中创建实例
之后调用intern方法,看这个对象是否在常量池中,发现在,就会返回常量池中的那个在加载过程中创建的"hello"实例的引用返回,一个是在加载的时候创建的"hello"实例,一个是运行时new出来的实例,所以==时是false。

栗3

	String s5 = "hadoop"+"spark";
	System.out.println(s5.intern()==s5);
	
	//运行结果
	true

JVM对字面量的直接赋值拼接情况做了优化,看了许多大牛的解释和字节码指令的流程,如果直接这样赋值,底层会用StringBuilder将这两个分开的字面量拼接成一个字符串,之后创建实例将引用放到常量池中。调用intern方法时发现常量池中存在,就返回常量池中的实例引用,s5在初始化是是直接字面量赋值,所以会先查看常量池中有没有,发现有,直接放回常量池中实例引用,所以==是true

	String s5 = "hadoop"+"spark";
//      等价于 String s5 = "hadoopspark";

总结:

String s6 = "haha";
这种创建String实例就是先看常量池中有没有,有就返回,没有就在常量池创建一个
String s7 = new String("haha");
这种创建String对象就是直接在堆中创建一个String实例

s8.intern方法,如果s8这个字符串在常量池中,就返回常量池中的实例引用,如果不在,就把这个堆中的字符串放到常量池中并返回实例引用。

结束
求毒打

  • 3
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 4
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值