JDK7将String常量池从Perm区移动到了Java Heap区。在JDK1.6中,intern方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中的实例。但是在JDK1.7以后,String.intern()方法不会在复制实例,只是在常量池中记录首次出现的实例引用。下面来看一些具体例子。

案例一:

        String str1 = new String("计算机") + new String("软件");
System.out.println(str1.intern() == str1); String str2 = new String("ja") + new String("va");
System.out.println(str2.intern() == str2);

输出结果:

JDK1.6  false  false
JDK1.7  true  false

分析:在JDK1.6中,intern方法会把首次遇到的字符串实例复制到永久代中,返回的也是永久代中的实例,而由new String()创建的字符串实例在Java堆中所以不是同一个引用,都返回false。但是在JDK1.7以后,String.intern()方法不会在复制实例,只是在常量池中记录首次出现的实例引用,因此str1.intern()=str1,但是Java字符串是Java中的关键字,早已创建,所以str2.intern()!=str2。当然如果用StringBuilder创建字符串效果也是一样的。

        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()==str2);

接下来再给两个特殊例子:

代码一:

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

代码二:

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

分析:

首先,jdk6中的解释

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

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

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

正是因为字符串常量池移动到 JAVA Heap 区域后,再来解释为什么会有上述的打印结果。

  • 在代码一中,先看 s3和s4字符串。String s3 = new String("1") + new String("1");,这句代码中现在生成了2个对象,一个是字符串常量池中的“1” 和 另一个是JAVA Heap 中的 s3引用指向的对象。中间还有2个匿名的new String("1")我们不去讨论它们。此时s3引用对象内容是"11",但此时常量池中是没有 “11”对象的。
  • 接下来s3.intern();这一句代码,是将 s3中的“11”字符串放入 String 常量池中,因为此时常量池中不存在“11”字符串,因此常规做法是跟 jdk6 图中表示的那样,在常量池中生成一个 "11" 的对象,关键点是 jdk7 中常量池不在 Perm 区域了,这块做了调整。常量池中不需要再存储一份对象了,可以直接存储堆中的引用。这份引用指向 s3 引用的对象。 也就是说引用地址是相同的。
  • 最后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 的引用地址明显不同。图中画的很清晰。

  • 来看代码二代码,从上边第二幅图中观察。第一段代码和第二段代码的改变就是 s3.intern(); 的顺序是放在String s4 = "11";后了。这样,首先执行String s4 = "11";声明 s4 的时候常量池中是不存在“11”对象的,执行完毕后,“11“对象是 s4 声明产生的新对象。然后再执行s3.intern();时,常量池中“11”对象已经存在了,因此 s3 和 s4 的引用是不同的。
  • 第二段代码中的 s 和 s2 代码中,s.intern();,这一句往后放也不会有什么影响了,因为对象池中在执行第一句代码String s = new String("1");的时候已经生成“1”对象了。下边的s2声明都是直接从常量池中取地址引用的。 s 和 s2 的引用地址是不会相等的。

接下来再看一个例子:

        String str1=new StringBuffer("计算机").append("软件").toString();
str1.intern();
String str2="计算机软件";
System.out.println(str1==str2);

输出结果:

JDK1.6 false

JDK1.7 true

分析:StringBuffer创建字符串和new String()创建字符串类似,分析过程如上。同样,如果str1.intern();和String str2="计算机";互换位置,则上述输出结果均为false。

        String str1=new StringBuffer("计算机").toString();
str1.intern();
String str2="计算机";
System.out.println(str1==str2);

输出结果:

JDK1.6 false

JDK1.7 false

总结:

  • JDK7将String常量池从Perm区移动到了Java Heap区
  • 调用String.intern()方法时,会在常量池中直接保存首次创建该对象的引用,而不会重新创建对象
  • 如果创建的字符串是关键字,则常量池中保存的是关键字的引用,而不是创建该关键字时的对象引用。
  • 如果字符串不是拼接而成,则在创建该字符串时就已经把对应字符串放进常量池中了。所以要想使str.intern=str,则此字符串必须是拼接而成(可以通过String或者StringBuffer对象创建)。

常量池之String.intern()方法的更多相关文章

  1. 字符串常量池和String.intern()方法在jdk1.6、1.7、1.8中的变化

    字符串常量池也是运行时常量池 jdk1.6中,它是在方法区中,属于“永久代” jdk1.7中,它被移除方法区,放在java堆中 jdk1.8中,取消了“永久代”,将常量池放在元空间,与堆独立了 pub ...

  2. 关于jvm中的常量池和String.intern()理解

    1. 首先String不属于8种基本数据类型,String是一个对象. 因为对象的默认值是null,所以String的默认值也是null:但它又是一种特殊的对象,有其它对象没有的一些特性. 2. ne ...

  3. Knowledge Point 20180309 字符串常量池与String,intern()

    引言 什么都先不说,先看下面这个引入的例子: public static void test4(){ String str1 = new String("SEU") + new S ...

  4. 对于JVM中方法区,永久代,元空间以及字符串常量池的迁移和string.intern方法

    在Java虚拟机(以下简称JVM)中,类包含其对应的元数据,比如类的层级信息,方法数据和方法信息(如字节码,栈和变量大小),运行时常量池,已确定的符号引用和虚方法表. 在过去(当自定义类加载器使用不普 ...

  5. String放入运行时常量池的时机与String.intern()方法解惑

    运行时常量池概述 Java运行时常量池中主要存放两大类常量:字面量和符号引用.字面量比较接近于Java语言层面的常量概念,如文本字符串.声明为final的常量值等. 而符号引用则属于编译原理方面的概念 ...

  6. JVM体系结构之七:持久代、元空间(Metaspace) 常量池==了解String类的intern()方法、常量池介绍、常量池从Perm-->Heap

    一.intern()定义及使用 相信绝大多数的人不会去用String类的intern方法,打开String类的源码发现这是一个本地方法,定义如下: public native String inter ...

  7. 基本数据类型的常量池与String类型常量池解析

    抛出样例: Integer a1  = new Integer(123);        Integer a2  = new Integer(123);        System.out.print ...

  8. String intern 方法 jdk中的描述

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

  9. 浅析String.intern()方法

    1.String类型“==”比较样例代码如下:package com.luna.test;public class StringTest { public static void main(Strin ...

随机推荐

  1. 一个基于JRTPLIB的轻量级RTSP客户端(myRTSPClient)——收流篇:(三)RTSP命令解析

    一.RTSP命令简述 RTSP是用来控制实时流媒体“播放”.“暂停”.“停止”.“录像”等行为的传输协议.该协议规定了一系列命令以及这些命令的发送格式,RTSP客户端可以通过发送这些指定的命令,从而控 ...

  2. 【Ubuntu 16】安装deb

    deb是debian linux的安装格式,跟red hat的rpm非常相似,最基本的安装命令是:dpkg -i file.deb  dpkg 是Debian Package的简写,是为Debian ...

  3. 使用webpack热加载,开发多页面web应用

    我们一般使用webpack热加载开发SPA应用,但工作中难免会遇到一些多页面的demo或项目. 故参考 kingvid-chan 的代码,搭了一个使用HRM开发多页面web应用的脚手架,刚好也进一步学 ...

  4. php 微信公众号+微商城开发 基于Thinkphp3.2框架开发

    说明:本教程是自己自学+自己的理解+扩展(包括学习过程中遇到的一些问题) 参考教程:麦子学院--李忠益--http://www.maiziedu.com/u/70409/ 微盟: http://www ...

  5. linux(十)配置ssh免密登录实现

    知道ssh的朋友应该知道它是用来干什么的,如果你不知道什么是ssh远程登录的话,可以去看一下我的上一篇博客,关于linux的网络基础的知识.备注:ssh是用于远端登入.执行ssh指令开启终端机阶段作业 ...

  6. Javaweb项目碰到的问题- Access denied for user 'root'@'localhost' (using password: YES)

    出现未给localhost root用户授权,主要是项目中存在的多个xxx.properties,其中用户名为root的password的值不完全相同导致的,使用eclipse的search 功能找到 ...

  7. java实现邮箱找密码

    SMTP,POP3,IMAP POP3 POP3是Post Office Protocol 3的简称,即邮局协议的第3个版本,它规定怎样将个人计算机连接到Internet的邮件服务器和下载电子邮件的电 ...

  8. spring boot / cloud (十六) 分布式ID生成服务

    spring boot / cloud (十六) 分布式ID生成服务 在几乎所有的分布式系统或者采用了分库/分表设计的系统中,几乎都会需要生成数据的唯一标识ID的需求, 常规做法,是使用数据库中的自动 ...

  9. js变量以及其作用域详解

    详见: http://blog.yemou.net/article/query/info/tytfjhfascvhzxcytp73   一.变量的类型  Javascript和Java.C这些语言不同 ...

  10. 机器学习实战之 第10章 K-Means(K-均值)聚类算法

    第 10 章 K-Means(K-均值)聚类算法 K-Means 算法 聚类是一种无监督的学习, 它将相似的对象归到一个簇中, 将不相似对象归到不同簇中.相似这一概念取决于所选择的相似度计算方法.K- ...