运行时常量池是方法区的一部分,存放编译期生成的字面量和符号引用,String的intern()方法在运行期间将新常量放入运行时常量池中
在程序的编译期,生成了class文件,代码中的类 ,方法,接口等的常量和字符串常量就放到class文件的常量池中。
在虚拟机加载class文加后,class文件中的常量池就被放到了运行时常量池,这个运行时常量池时方法区的一部分。
运行时常量池有大小限制,一般为4M,如果超出这个限制可能就要抛出OOM异常,往常量池中添加数据,类的加载是一种方式,还有一种用的比较多的方式是String的intern()方法。
简单来说,intern方法是拿字符串到常量池中比较,如果运行时常量池中有该字符串,则返回其引用,若没有,则将该字符串添加到运行时常量池中。
intern方法在文档中的解释:
Returns a canonical representation for the string object.
A pool of strings, initially empty, is maintained privately by the class String
.
When the intern method is invoked, if the pool already contains a string equal to this String
object as determined by the equals(Object)
(euqal方法比较String的内容是否相同,而不比较引用是否相同)method, then the string from the pool is returned. Otherwise, this String
object is added to the pool and a reference to this String
object is returned.
It follows that for any two strings s
and t
, s.intern() == t.intern()
is true
if and only if s.equals(t)
is true
.
All literal strings and string-valued constant expressions are interned. String literals are defined in §3.10.5 of the
Java
举一些面试中常见的例子
public class Intern {
public static void main(String[] args) {
System.out.println(System.getProperty("java.vm.version")); //打印java虚拟机版本
String s = new String("1")+“1”; //用这种方式初始化s是为了不在类加载阶段就将11放到常量池中,生成的s存放在heap中
s.intern();
String s2 = "11";
System.out.println(s == s2);
}
}
发现在不同版本的jdk上编译运行会输出不同的结果
在1.6:
在1.7:
出现两种不同结果;常量池在VM的方法区,方法区在hotspot虚拟机上一版用永久代来实现,因此也是有MaxPermSize上限的,是在jdk1.7后,为了防止常量池溢出,将常量池移到了heap区,也就是实现对象内存分配的堆内存。
如此一来,当执行s.intern()时,常量池所在区域与s对象所在区域都是heap,于是常量池保存了该字符串对象的引用s到常量池。
当生成s2时,由于常量池中已经有11字符串,直接将该引用返回,于是s2指向常量池s,也就是堆内存中s的对象,所以用==比较s与s2的地址时,返回了true。