首先介绍下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. hbase 原子操作cas

    在高并发的情况下,对数据row1  column=cf1:qual1, timestamp=1, value=val1的插入或者更新可能会导致非预期的情况, 例如:原本客户端A需要在value=val ...

  2. pragma指令详解(转载)

    #pragma comment( comment-type [,"commentstring"] ) 该宏放置一个注释到对象文件或者可执行文件.comment-type是一个预定义 ...

  3. 【BZOJ4774】修路(动态规划,斯坦纳树)

    [BZOJ4774]修路(动态规划,斯坦纳树) 题面 BZOJ 题解 先讲怎么求解最小斯坦纳树. 先明白什么是斯坦纳树. 斯坦纳树可以认为是最小生成树的一般情况.最小生成树是把所有给定点都要加入到联通 ...

  4. 【转】器件为什么只听英文Datasheet的话

    浅谈为什么要阅读英文数据手册 ——带你Go Through Datasheet 系列 Unfortunately!从事软硬件(固件)开发的工程师都知道,我们所用的元器件,特别是高端器件和芯片,都是来自 ...

  5. 安装和使用 PyInstaller 遇到的问题

    写在前面 在学习 Python语言程序设计 的时候,其中有一节课提到了 PyInstaller 第三方库.PyInstaller 可以用来打包 python 应用程序,打包完的程序就可以在没有安装 p ...

  6. rovio视觉里程计的笔记

    rovio是一个紧耦合,基于图像块的滤波实现的VIO. 他的优点是:计算量小(EKF,稀疏的图像块),但是对应不同的设备需要调参数,参数对精度很重要.没有闭环,没有mapping thread.经常存 ...

  7. SSH框架搭建问题总结

    1.eclipse中tomcat配置是否正确?能否在网页中访问的到? 如何在eclipse中配置tomcat就不说了,我们看下问题,在网页上访问tomcat的地址,为什么出现404错误呢? 解决办法: ...

  8. Chapter 5(串)

    1.kmp #include <stdio.h> #include <stdlib.h> #include <stdbool.h> #include <str ...

  9. 三、Linux学习之命令基本格式篇

    一.命令格式 命令 [选项] [参数] 注意: 1.和别命令使用不遵循此格式 2.当有多个选项时,可以写在一起 3.简化选项与完整选项(-a 等价于--all) 4.中括号为可选,意思是可以有可以没有 ...

  10. 利用Snapshot快速跨Region迁移服务器

    当你需要对现有的网站进行跨区域迁移,或者是部署DR Site的时候,又不希望重新部署应用,有什么好办法呢?其实你可以利用Azure的磁盘snapshot进行磁盘级的复制,这样可以减少很多部署应用的时间 ...