首先介绍下intern方法:

如果常量池中存在当前字符串, 就会直接返回当前字符串. 如果常量池中没有此字符串, 会将此字符串放入常量池中后, 再返回。
  • 1
  • 2

在《深入理解Java虚拟机》一书中,对于intern方法有个这样的例子:

public class RuntimeConstantPoolOOM{
public static void main(String[] args){
String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println(str1.intern() == str1);
String str2 = new StringBuilder("ja").append("va").toString();
System.out.println(str2.intern() == str1);
}
}

上段代码在JDK 1.6中运行,会得到两个false,而在JDK 1.7中运行,会得到一个true和一个false。产生差异的原因是:在JDK 1.6中,intern方法会把首次遇到的字符串实例复制到永久代中(常量池中),返回的也是永久代中这个字符串实例的引用,而由StringBuilder创建的字符串实例在Java堆上,所以必然不是同一个引用,将会返回false。而JDK 1.7的intern实现不会再复制实例,只是在常量池中记录首次出现的实例引用,因此intern返回的引用和由StringBuilder创建的那个字符串实例是同一个。对str2比较返回false是因为“java”这个字符串在执行StringBuilder.toString()之前已经出现过,字符串常量池已经有了它的引用,不符合“首次出现”的原则,而“计算机软件”这个字符串则是首次出现的,因此返回true。

如果上面的解释不清楚,可以配合下面的例子加深理解: 
以下举例和说明来自:http://www.cnblogs.com/wxgblogs/p/5635099.html

public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}

打印结果是

  • jdk6 下false false
  • jdk7 下false true

1、jdk 6中的解释 

注:图中绿色线条代表 string 对象的内容指向。 黑色线条代表地址指向。

如上图所示。首先说一下 jdk6中的情况,在 jdk6中上述的所有打印都是 false 的,因为 jdk6中的常量池是放在 Perm 区中的,Perm区和正常的 JAVA Heap 区域是完全分开的。上面说过如果是使用引号声明的字符串都是会直接在字符串常量池中生成,而 new 出来的 String 对象是放在 JAVA Heap 区域。所以拿一个 JAVA Heap 区域的对象地址和字符串常量池的对象地址进行比较肯定是不相同的,即使调用String.intern方法也是没有任何关系的。

2、jdk 7中的解释 
在 Jdk6 以及以前的版本中,字符串的常量池是放在堆的Perm区的,Perm区是一个类静态的区域,主要存储一些加载类的信息,常量池,方法片段等内容,默认大小只有4m,一旦常量池中大量使用 intern 是会直接产生java.lang.OutOfMemoryError:PermGen space错误的。在 jdk7 的版本中,字符串常量池已经从Perm区移到正常的Java Heap区域了。为什么要移动,Perm 区域太小是一个主要原因,当然据消息称jdk8已经直接取消了Perm区域,而新建立了一个元区域。应该是jdk开发者认为Perm区域已经不适合现在 JAVA 的发展了。正式因为字符串常量池移动到JAVA Heap区域后,再来解释为什么会有上述的打印结果。 

为方便讲解,再次附上代码段:

public static void main(String[] args) {
String s = new String("1");
s.intern();
String s2 = "1";
System.out.println(s == s2); String s3 = new String("1") + new String("1");
s3.intern();
String s4 = "11";
System.out.println(s3 == s4);
}
  • 先看 s3和s4字符串。String s3 = new String(“1”) + new String(“1”);,这句代码中现在生成了2个最终对象,分别是字符串常量池中的“1” 和 JAVA Heap中的 s3引用指向的对象。(中间还有2个匿名的new String(“1”)我们不去讨论它们)此时s3引用对象内容是”11″,但此时常量池中是没有 “11”对象的。

  • 接下来s3.intern(); 在JDK 6中,这一句代码,是将 s3中的”11”字符串放入String 常量池中,因为此时常量池中不存在”11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个”11”的对象

  • 关键点是 jdk7 中常量池不在Perm区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向s3引用的对象。 也就是说常量池中存储的不再是一个“11”,而是指向“11”的引用,即堆中的对象,所以引用地址是相同的。

  • 最后String s4 = “11”; 这句代码中”11″是显示声明的,因此会直接去常量池中创建,创建的时候发现已经有这个对象了,此时也就是指向s3引用对象的一个引用。所以s4引用就指向和s3一样了。因此最后的比较 s3 == s4 是 true。

  • 再看s和 s2 对象。String s = new String(“1”); 第一句代码,生成了2个对象。分别是:常量池中的“1” 和 JAVA Heap 中的字符串对象。s.intern(); 这一句是 s 对象去常量池中寻找后发现 “1” 已经在常量池里了。

  • 接下来String s2 = “1”; 这句代码是生成一个 s2的引用指向常量池中的“1”对象。 结果就是 s 和 s2 的引用地址明显不同。图中画的很清晰。 

再来看第二段代码:

public static void main(String[] args) {
String s = new String("1");
String s2 = "1";
s.intern();
System.out.println(s == s2); String s3 = new String("1") + new String("1");
String s4 = "11";
s3.intern();
System.out.println(s3 == s4);
}

打印结果是

  • jdk6 下false false
  • jdk7 下false false

  • 第一段代码和第二段代码的改变就是 s3.intern(); 的顺序是放在String s4 = “11”;后了。这样先执行String s4 = “11”;声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,不需要新建任何对象或引用; 因此 s3 和 s4 的引用是不同的。(一个指向常量池,一个指向Java堆)。

  • 第二段代码中的 s 和 s2 代码中,s.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String(“1”);的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。

看最后一段代码

public static void main(String[] args) {
String s = new String("");
String s2 = "";
s.intern();
System.out.println(s == s2); String s1 = "";
String s3 = new String("") + new String("");
s3.intern();
String s4 = "";
System.out.println(s3 == s4);
}

打印结果是

  • jdk6 下false false
  • jdk7 下false fals

  • jdk6中没变化
  • jdk7中String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象(如果常量池已经堆中对象的值不会保存,看最后一段代码)。

小结

从上述的例子代码可以看出 jdk7 版本对 intern 操作和常量池都做了一定的修改。主要包括2点:

  • 将String常量池从Perm区移动到了Java Heap区(使用引号时仍然会在常量池中检查并创建对象和jdk6中一样)

  • String#intern 方法时,如果存在堆中的对象,会直接保存对象的引用,而不会重新创建对象(如果常量池已经堆中对象的值不会保存,看最后一段代码)。

JDK 6和JDK 7的intern方法之不同的更多相关文章

  1. String的Intern方法详解

    引言 在 JAVA 语言中有8中基本类型和一种比较特殊的类型String.这些类型为了使他们在运行过程中速度更快,更节省内存,都提供了一种常量池的概念.常量池就类似一个JAVA系统级别提供的缓存.8种 ...

  2. String intern 方法 jdk中的描述

    一个初始为空的字符串池,它由类 String 私有地维护. 当调用 intern 方法时,如果池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法确定),则返回池中 ...

  3. 不同JDK版本之间的intern()方法的区别-JDK6 VS JDK6+

    String s = new Stirng(“a”); s.intern(); JDK6:当调用intern()方法时,如果字符串常量池先前已创建出该字符串对象,则返回池中的该字符串的引用.否则,将此 ...

  4. [译]JDK 6 and JDK 7中的subString()方法

    (说明,该文章翻译自The substring() Method in JDK 6 and JDK 7) 在JDK 6 and JDK 7中的substring(int beginIndex, int ...

  5. 在JDK 6和JDK 7的substring()方法的区别?

    原文链接:https://www.programcreek.com/2013/09/the-substring-method-in-jdk-6-and-jdk-7/ 在JDK 6和JDK 7中subs ...

  6. eclipse中使用maven创建项目JDK版本默认是1.5解决方法

    请看解决方案: 1. 修改maven的settings.xml文件. 添加以下行,jdk版本改为自己需要的版本: <profile> <id>jdk-1.7</id> ...

  7. Java intern()方法

    intern()方法: public String intern() JDK源代码如下图: 返回字符串对象的规范化表示形式. 一个初始时为空的字符串池,它由类 String 私有地维护. 当调用 in ...

  8. 常量池之String.intern()方法

    JDK7将String常量池从Perm区移动到了Java Heap区.在JDK1.6中,intern方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中的实例.但是在JDK1.7以后,Str ...

  9. String的Intern方法

    jdk6 和 jdk7 下 intern 的区别 相信很多 JAVA 程序员都做做类似 String s = new String("abc")这个语句创建了几个对象的题目. 这种 ...

随机推荐

  1. XOR and Favorite Number CodeForces - 617E(前缀异或+莫队)

    题意原文地址:https://blog.csdn.net/chenzhenyu123456/article/details/50574169 题意:有n个数和m次查询,每次查询区间[l, r]问满足a ...

  2. 从商用到开源:15个维度,全面剖析DB2与MySQL数据库的差异

    随着MySQL数据库的应用越来越广泛,DB2向MySQL数据库的迁移需求也越来越多.进行数据库之间迁移的时候,首先遇到的并且也是最基本最重要的就是两种数据库数据类型之间的转换. 相关阅读: 从商用到开 ...

  3. 阿里大鱼短信发送,放到项目中报错Java.lang.NoClassDefFoundError:com/aliyuncs/exceptions/ClientException,已解决

    由于项目中使用的短信服务发送的消息太慢,所以把采用了阿里大鱼的短信服务,花费了几个小时,通过审核,发现可以单独运行.但是,放到web项目中会报错(Java.lang.NoClassDefFoundEr ...

  4. JUnit中按照顺序执行测试方式

    很多情况下,写了一堆的test case,希望某一些test case必须在某个test case之后执行.比如,测试某一个Dao代码,希望添加的case在最前面,然后是修改或者查询,最后才是删除,以 ...

  5. BZOJ2213 [Poi2011]Difference 【乱搞】

    题目链接 BZOJ2213 题解 考虑任意一对点的贡献,单独拿出那些点所在位置 一个设为\(1\),一个设为\(-1\),从头到尾扫一遍维护前缀和,以及当前最小前缀和 两者相减更新答案 需要注意的是当 ...

  6. 【uoj33】 UR #2—树上GCD

    http://uoj.ac/problem/33 (题目链接) 题意 给出一棵${n}$个节点的有根树,${f_{u,v}=gcd(dis(u,lca(u,v)),dis(v,lca(u,v)))}$ ...

  7. CentOS安装oracleJDK

    实践证明,oracleJDK比openJDK快不止一星半点,换了之后立马从满负载降到个位数负载. 1.卸载openjdk # rpm -qa | grep -E '^(java|jdk)' # yum ...

  8. 解题:NOI 2016 优秀的拆分

    题面 其实题目不算很难,但是我调试的时候被玄学了,for循环里不写空格会RE,写了才能过.神**调了一个多小时是这么个不知道是什么的玩意(真事,可以问i207M=.=),心态爆炸 发现我们只要找AA或 ...

  9. 解题:CF983B pyramid

    题面 题目都告诉我们是“金字塔”了,不妨分析分析$f$的性质 $f(a_1,a_2)=f(a_1$ $xor$ $a_2)=a1$ $xor$ $a_2$ $f(a_1,a_2,a_3)=f(a_1$ ...

  10. 2018 “百度之星”程序设计大赛 - 初赛(A)度度熊学队列 list rope

    c++ list使用 #include <cstdio> #include <cstdlib> #include <cmath> #include <cstr ...