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

时间:2023-02-10 23:39:17

运行时常量池概述

Java运行时常量池中主要存放两大类常量:字面量和符号引用。字面量比较接近于Java语言层面的常量概念,如文本字符串、声明为final的常量值等。

而符号引用则属于编译原理方面的概念,包括了下面三类常量:

- 类和接口的全限定名(包名+类名)

- 字段的名称和描述符

- 方法的名称和描述符

运行时常量池位置

运行时常量池在JDK1.6及之前版本的JVM中是方法区的一部分,而在HotSpot虚拟机中方法区放在了”永久代(Permanent Generation)”。所以运行时常量池也是在永久代的。

但是JDK1.7及之后版本的JVM已经将运行时常量池从方法区中移了出来,在Java 堆(Heap)中开辟了一块区域存放运行时常量池


本文主要解惑String对象(即文本字符串)何时放入常量池,不涉及上述三类符号引用常量和其他非String常量值。而且本文只讨论主流的HotSpot虚拟机。

String何时放入常量池

记住一句话:直接使用双引号声明出来的String对象会直接存储在常量池中。

代码一:

String a = "计算机软件";

分析:因为计算机软件五个字直接使用了双引号声明,故JVM会在运行时常量池中首先查找有没有该字符串,有则直接返回该字符串在常量池中的引用;没有则直接在常量池中创建该字符串,然后返回引用。此时,该句代码已经执行完毕,不会在java Heap(堆)中创建内容相同的字符串。该字符串只在常量池中创建了一个String对象。


代码二:

String a = new String("计算机软件");

分析:该行代码生成了两个String对象(Stack(栈)中的对象引用不在讨论范围内):第一步,因为计算机软件五个字直接使用了双引号声明,故JVM会在运行时常量池中首先查找有没有该字符串,有则进入第二步;没有则直接在常量池中创建该字符串,然后进入第二步。第二步:在常量池中创建了一个String对象之后,由于使用了new,JVM会在Heap(堆)中创建一个内容相同的String对象,然后返回堆中String对象的引用。该行代码分别在常量池和堆中生成了两个内容相同的String对象。


代码三:

String a = "计算机" + "软件";

分析:由于JVM存在编译期优化,对于两个直接双引号声明的String的+操作,JVM在编译期会直接优化为“计算机软件”一个字符串,故该行代码同代码一


代码四:

String b = "计算机";
String a = b + "软件";

分析:由于b是一个String变量,编译期无法确定b的值,故不会优化为一个字符串。即使我们知道b的值,但JVM认为它是个变量,变量的值只能在运行期才能确定,故不会优化。运行期字符串的+连接符相当于new,故该行代码在Heap中创建了一个内容为“计算机软件”的String对象,并返回该对象的引用。至此,该代码执行完毕,因为没有直接双引号声明计算机软件这5个字的字符串,故常量池中不会生成计算机软件这5个字的字符串。但是会有“计算机”和“软件”这两个String对象,因为他们都用双引号声明了。


代码五:

String final b = "计算机";
String a = b + "软件";

分析:该代码与代码四的唯一区别是将b声明为final类型,即为常量。故在编译期JVM能确定b的值,所以对+可以优化为“计算机软件”5个字的字符串。该代码的运行同代码三和代码一


代码六:

String a = new String("计算机") + "软件";

分析:因为有new,该代码也无法编译期优化,故该行代码只是在Heap中生成了“计算机软件”字符串的String对象,在常量池中没有内容相同的对象生成。


String.intern方法

概述

String.intern()是一个Native方法,它的作用是:如果运行时常量池中已经包含一个等于此String对象内容的字符串,则返回常量池中该字符串的引用;如果没有,则在常量池中创建与此String内容相同的字符串,并返回常量池中创建的字符串的引用。

JDK1.7改变

当常量池中没有该字符串时,JDK7的intern()方法的实现不再是在常量池中创建与此String内容相同的字符串,而改为在常量池中记录Java Heap中首次出现的该字符串的引用,并返回该引用

验证代码:

String str1 = new StringBuilder("计算机").append("软件").toString();
System.out.println((str1.intern() == str1));
//JDK1.6:false
//JDK1.7:true

String b = "计算机";
String a = b + "软件";
System.out.println(a.intern() == a);
//JDK1.6:false
//JDK1.7:true    

测试代码

请运行以下的代码看看你分析的结果和真正的运行结果是否一样,JDK1.6和1.7都要跑一遍,如果你都分析对了,那就是理解了。

//一次放开一个多行注释运行
       /*
        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);
        */
       /*
        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);
        */
  /*
 //+连接但编译器不优化
        String s1=new String("xy") + "z";
        String s2=s1.intern();
        System.out.println( s1==s1.intern() );
        System.out.println( s1+" "+s2 );
        System.out.println( s2==s1.intern() );
        */
      /*// 一般情况
        String s1=new String("xyz") ;
        String s2=s1.intern();
        System.out.println( s1==s1.intern() );
        System.out.println( s1+" "+s2 );
        System.out.println( s2==s1.intern() );
        */

       /*//编译器优化
        String s1 = "xy" + "z";
        String s2 = s1.intern();
        System.out.println( s1==s1.intern() );
        System.out.println( s1+" "+s2 );
        System.out.println( s2==s1.intern() ); 

        */

说明:本文有部分内容摘抄了周志明大神的《深入理解Java虚拟机》一书,部分代码参考了网上多个博客的内容,仅用于学习。无意侵犯版权。

参考:

《深入理解Java虚拟机-JVM高级特性与最佳实践》周志明著

JVM常量池和八种基本数据及字符串

深入解析String#intern

Java永久代去哪儿了

String放入运行时常量池的时机与String.intern()方法解惑的更多相关文章

  1. 运行时常量池中的符号引用/String.intern() /ldc指令

    运行时常量池,之前放在方法区(永久代)中,1.8之后被转移到元空间,放到了native memory中. 具体的数据结构是:(看对象的内存布局,句柄访问还是对象头中保存指向类的元数据的指针,这里以对象 ...

  2. JVM笔记3-java内存区域之运行时常量池

    1.运行时常量池属于线程共享区中的方法区. 2.运行时常量池用于编译期生成的各种自变量,符号引用,这部分内用将在类加载后接入方法区的运行时常量池中存放. 看如下代码所示,如图: public clas ...

  3. Java 运行时常量池

    运行时常量池是方法区的一部分.class中除了有类的版本,字段,方法,接口等描述信息外,还有一项信息是常量池,用于存放编译期生成的各种字面量和符号引用,这部分内容将在类加载后存放在方法区的运行时常量池 ...

  4. JVM-String常量池与运行时常量池

    Start with JVM 周志明先生著-<深入理解Java虚拟机>,书买回来好几天了,但是最近才准备开始搞一搞了(哭瞎…..).首先是第一章的Java以及JVM发展历史,大概知道了现行 ...

  5. JVM 常量池、运行时常量池、字符串常量池

    常量池: 即class文件常量池,是class文件的一部分,用于保存编译时确定的数据. 保存的内容如下图: D:\java\test\out\production\test>javap -ver ...

  6. 彻底搞清楚class常量池、运行时常量池、字符串常量池

    彻底搞清楚class常量池.运行时常量池.字符串常量池 常量池-静态常量池 也叫 class文件常量池,主要存放编译期生成的各种字面量(Literal)和符号引用(Symbolic Reference ...

  7. Class常量池、运行时常量池、字符串常量池的一些思考

    Class常量池.运行时常量池.字符串常量池 class常量池 java代码经过编译之后都成了xxx.class文件,这是java引以为傲的可移植性的基石.class文件中,在CAFEBABE.主次版 ...

  8. JVM详解之&colon;运行时常量池

    目录 简介 class文件中的常量池 运行时常量池 静态常量详解 String常量 数字常量 符号引用详解 String Pool字符串常量池 总结 简介 JVM在运行的时候会对class文件进行加载 ...

  9. 类的加载,链接和初始化——1运行时常量池&lpar;来自于java虚拟机规范英文版本&plus;本人的翻译和理解&rpar;

    加载(loading):通过一个特定的名字,找到类或接口的二进制表示,并通过这个二进制表示创建一个类或接口的过程. 链接:是获取类或接口并把它结合到JVM的运行时状态中,以让类或接口可以被执行 初始化 ...

随机推荐

  1. Windows下Eclipse&plus;Scala&plus;Spark开发环境搭建

    1.安装JDK及配置java环境变量 本文使用版本为jdk1.7.0_79,过程略 2.安装scala 本文使用版本为2.11.8,过程略 3.安装spark 本文使用版本为spark-2.0.1-b ...

  2. 使用spring-data-redis做缓存

    说到缓存,首先肯定是spring-cache了. spring-cache使用一个CacheManager来进行管理缓存,为了使用我们的redis作为缓存源,需要向spring注册一个CacheMan ...

  3. Tsung测试Tigase

    用两台主机坐Tigase的Tsung测试,其中1台运行Tigase,另1台运行Tsung. 1.Tigase服务器设置 tigase.conf: #osgiEnabled=(true|false) # ...

  4. &lowbar;config&period;json

    { "AUTH": "66D86F40DF42A6103C2B0C2F16E41472DABF0594C79859E5EF51E06B377215F3B464E3F0F3 ...

  5. diahosting的低配vps弱爆了

    以下仅为一个用户的心声 上年年中的时候买了dia的128M vps,算是我第一个vps.工作以来,我弄了一个wp博客,所以我在上面搭了apache的服务器,但是由于内存低,挂得也快.后来我换了Ngin ...

  6. java中enum类型的使用

    java 枚举类型enum 的使用 最近跟同事讨论问题的时候,突然同事提到我们为什么java 中定义的常量值不采用enmu 枚举类型,而采用public final static 类型来定义呢?以前我 ...

  7. C&num; 无边框窗体移动代码

    C# 无边框窗体移动代码 Point _frmPoint = new Point(); //移动前窗体左上角坐标 Point _mousePoint = new Point(); //按下鼠标时坐标 ...

  8. Web API 2

    Asp.Net Web API 2 官网菜鸟学习系列导航[持续更新中]   前言 本来一直参见于微软官网进行学习的, 官网网址http://www.asp.net/web-api.出于自己想锻炼一下学 ...

  9. 各种浏览器开启JavaScript脚本方法

    各种浏览器开启JavaScript脚本方法 随着网站设计技术的发展,为了用户友好体验,大部分网站使用了JavaScript脚本设计,如果您的浏览器禁用或关闭的JavaScript支持,那么可能造成网站 ...

  10. MySQL,Oracle,PostgreSQL 数据库web维护客户端管理工具

    TreeDMS数据库管理系统使用JAVA开发,采用稳定通用的springMVC +JDBC架构,实现基于WEB方式对 MySQL,Oracle,PostgreSQL 等数据库进行维护管理操作. 功能包 ...