本文是在阅读《深入理解java虚拟机》2.4.3节时受到启发而写的,书中对这个知识点讲得也比较清楚了,这里自己再总结一下,加深理解。

文中根据书中的内容加入了自己的一些理解,如有理解不对的地方,欢迎讨论交流。

首先来看如下两个测试用例,执行完成后分别输出什么?

答案是testStringIntern1()输出true,testStringIntern2()输出false,如果是在jdk6中测试,答案分别是false, false,为什么会这样,如果明白了这个问题,对jvm内存结构会有个更清晰的认识。

public class StringTest {@Testpublic void testStringIntern1() {String a = new StringBuilder().append("I love ").append("java").toString();String intern = a.intern();System.out.println(intern == a);}@Testpublic void testStringIntern2() {String a = new StringBuilder().append("I love ").append("java").toString();String b = "I love java";String intern = a.intern();System.out.println(intern == a);}
}

我们知道,jvm内存包含堆区和方法区两个区域(这里不考虑其他区域),堆区用来存放对象实例,方法区保存类型信息,常量,静态变量,即时编译后的代码缓存等。方法区是jvm规范中定义的一个jvm内存区域,jvm规范只是规定了方法区应该存放什么类型的数据,但是这些数据真正要存在哪里,要看具体用的哪个虚拟机,不同的jvm可能会有不同的实现。

我们先来看一下《深入理解java虚拟机》是怎么描述hotspot虚拟机的方法区的。

从上面的描述我们可以知道,hotspot虚拟机中的方法区具体实现是在永久代(jdk7及之前)或元空间(jdk7之后),但是上面这段话说得有点模糊,注意文中说的,“到了JDK 7的HotSpot,已经把原本放在永久代的字符串常量池、静态变量等移出”,但是这里并没有说字符串常量池和静态变量移出是移出到了哪里,实际上是移到了堆中,也就是字符串常量和静态变量实际上是保存在堆中,虽然从概念是来说它们是属于方法区的数据。注意还有一句话,“还剩余的内容(主要是类型信息)全部移到了元空间中”,也就是说jdk8及之后,字符串常量和静态变量是保存在堆中,类型信息保存在元空间中,但是字符串常量、静态变量、类型信息在概念上都是属于方法区的。

这里有必要再讲一个intern方法的作用,以及intern方法在jdk6及jdk7中的不同。

jdk中对intern方法的描述。

这里引用一下这篇文章(https://www.cnblogs.com/itplay/p/11137526.html)中对intern方法的解释,在 JDK 1.6 中,调用 intern() 首先会在字符串池中寻找 equal() 相等的字符串,假如字符串存在就返回该字符串在字符串池中的引用;假如字符串不存在,虚拟机会重新在永久代上创建一个实例,并在字符串池中增加一指向这个新创建的实例。在 JDK 1.7 中,由于字符串池不在永久代了,intern() 做了一些修改,更方便地利用堆中的对象。字符串存在时和 JDK 1.6一样,但是字符串不存在时不再需要重新创建实例,可以直接指向堆上的实例。

我们还需要知道字符串常量池中存的是什么。首先需要明白,字符串常量池中保存的不是字符串对象,而是字符串对象的引用。那字符串对象保存在哪里呢?这个要看这个字符串是怎么生成的以及jdk的版本。具体差异如下表所示。

jdk版本 字符串定义方式 结果
6 String a = "a" 在永久代内存中创建了一个值为"a"的字符串对象,并在字符串常量池中添加一个引用指向这个对象。
String a = new String("a")

在永久代内存中创建了一个值为"a"的字符串对象,并在字符串常量池中添加一个引用指向这个对象。

同时在堆中创建一个值为"a"的字符串,变量a指向堆中的这个字符串对象。

7 String a = "a" 在堆内存中创建了一个值为"a"的字符串对象,并在字符串常量池中添加一个引用指向这个对象。
String a = new String("a")

在堆内存中创建了一个值为"a"的字符串对象,并在字符串常量池中添加一个引用指向这个对象。

同时在堆内存中再创建一个值为"a"字符串对象,变量a指向这个对象。

有了上面的知识背景,下面我们来分析testStringIntern1方法。

执行到下面这一句时,java堆内存中会有一个String对象,值为“I love java”,变量a指向这个对象,由于没有通过字面量方式定义,所以在字符串常量池中是没有一个指针是指向值为"I love java"的字符串对象的。

String a = new StringBuilder().append("I love ").append("java").toString();

接着,执行下面的intern方法时,程序会去字符串常量池中查找有没有和变量a,也就是“I lova java”相等(equals方法相等)的字符串,此时字符串常量池中还是没有的。但是由于堆内存中已经有了一个值为"I love java"的字符串对象,所以在jdk7及以后的版本中会直接复用堆内存中的这个字符串对象,把这个字符串对象的引用放到字符串常量池中,这样就相当于字符串常量池中有了"I love java"这个字符串对象。所以返回的结果和a变量是指向同一个字符串对象的,所以intern==a结果为true。在jdk6时的hotspot虚拟机,由于字符串常量池是在永久代中实现,所以调用intern时会在永久代查找“I love java”,找不到,就会把堆内存中的“I love java”字符串对象拷贝一个到永久代中,然后返回永久代中的引用,那么intern和a用==比较就会返回false。

String intern = a.intern();

接下来分析testStringIntern2方法。

第一句代码和上面一样,执行完后会在堆内存中创建一个"I love java"的字符串对象,但是要注意这个对象我们不是通过字面量形式定义的,它和其他普通java对象是一样的,仅仅只是堆中的一个对象,和字符串常量池之间没有任何关系。

下面这句代码定义了一个字符串字面量,字面量形式的字符串对象在jdk7中会放在堆内存中,在jdk6会放在永久代,但是不管放在哪里,此时字符串常量池中已经有了一个引用,指向这个值为"I love java"的字符串对象,这个对象和上面a指向的不是同一个对象。

String b = "I love java";

然后调用a.intern()方法时,同样,程序会在字符串常量池中查找有没有“I love java”字符串对象,发现已经有了,也就是我们上面定义的b,于是返回已有的这个字符串对象,其实就是返回b指向的那个对象的引用,由于b指向的对象和a不是同一个,所以intern == a为false。大家可以试验一下,b == intern是为true的,也就是a.intern()返回的就是b指向的那一个对象的引用。

总结:

intern方法在不同jdk中不同的表现是学习和理解jvm内存结构很好的一个例子,好好理解一下上面的测试用例对理解jvm内存结构会很有帮助。

参考:

《深入理解java虚拟机》

https://www.cnblogs.com/itplay/p/11137526.html

java中一个有意思的字符串intern问题相关推荐

  1. 使用java中replaceAll方法替换字符串中的反斜杠

    今天在项目中使用java中replaceAll方法将字符串中的反斜杠("\")替换成空字符串(""),结果出现如下的异常: 1 java.util.regex. ...

  2. java找重复字符串_在java中怎样查找重复字符串

    在一段java编程代码中,字符串是不可缺少的一个要素,属于java中的基础知识,字符串不仅在java面试题中会出现,在编写代码时更要掌握怎样使用字符串.在前面我们也学习过关于字符串截取的知识,你应该有 ...

  3. 字符和字节详解、Java中字节串和字符串相互转换

    字符.字节和编码 1. 程序中的字符与字节 字节是规定存储大小的存储单位,规定为8位一字节(8bit = 1 byte). 字符是人类的描述符号.存储在计算机时,不同的编码格式会有不同的字节组合,一般 ...

  4. c语言中大写英文字母所占字节,Java中字符编码和字符串所占字节数 .

    首 先,java中的一个char是2个字节.java采用unicode,2个字节来表示一个字符,这点与C语言中不同,C语言中采用ASCII,在大多数 系统中,一个char通常占1个字节,但是在0~12 ...

  5. java中的字符,字符串,数字之间的转换(亲测)

    string 和int之间的转换 string转换成int  :Integer.valueOf("12") int转换成string : String.valueOf(12) ch ...

  6. java中的字符,字符串,数字之间的转换

    java中的字符,字符串,数字之间的转换 string 和int之间的转换 string转换成int :Integer.valueOf(" ") int转换成string : St ...

  7. java 中利用subString 截取字符串中第三个/后面的内容,并将/用代替

    原文地址为: java 中利用subString 截取字符串中第三个"/"后面的内容,并将/用>代替 private String extractString(String ...

  8. 43、在java中一个类被声明为final类型,表示了什么意思?

    43.在java中一个类被声明为final类型,表示了什么意思? 表示该类不能被继承,是顶级类. JAVA面试问题及答案大全

  9. java类名可以是数字吗_在 Java 中,一个类可同时定义许多同名的方法,这些方法的形式参数的个数、类型或顺序各不相同,传回的值也可以不相同。这种面向对象程序特性称为( )。_学小易找答案...

    [简答题]Java 支持多继承吗 ? [单选题]以下关于继承的叙述正确的是( ). [单选题]在 Java 中,一个类可同时定义许多同名的方法,这些方法的形式参数的个数.类型或顺序各不相同,传回的值也 ...

最新文章

  1. 2020斐讯k3刷什么固件_斐讯K2/K3/K2P等路由器搭建收费wifi集成教程
  2. 保护模式 对CPL,RPL,DPL 的总结
  3. 有了内阻值,怎么判断电池是否健康?
  4. JAVA中Final的用法
  5. 在JUnit中处理异常的3种方法。 选择哪一个?
  6. Apple Watch UX流程套件 Fresh模板
  7. 广西电力职业技术学院计算机应用,广西电力职业技术学院电子与信息工程系
  8. 文件I/O(不带缓冲)之read函数
  9. 关于redis key命名规范的设计
  10. 【测试】对网易邮箱登录的测试流程
  11. 第十五天 13-linux防火墙
  12. 盗墓笔记《云顶天宫》好不好看?当贝投影F3画面还原度如何?
  13. python保留字-Python保留字
  14. Oracle索引梳理系列(六)- Oracle索引种类之函数索引
  15. 原 the app referencesnon-public selectors in payload
  16. 0032-PAT满分行动第二天:简单模拟1046、1008、1012
  17. set.seed的作用
  18. 10进制和16进制 数字和ASCII码互转
  19. 工行B2B异步通知中文出现乱码?请大神帮忙
  20. 第八周拓展实践3换分币

热门文章

  1. 关于前端的HTML+CSS基础知识汇总(较为全面)
  2. 【算法练习】字符串处理 poj2690:首字母大写
  3. win10计算机管理器在哪,Windows10开启服务管理器图文教程|Win10系统服务管理器在哪...
  4. python文本txt处理
  5. iview使用render函数渲染嵌套表格
  6. Android 之路35---Fragment
  7. 传奇服务端游戏中禁止或者允许删除人物怎么设置的?
  8. Ubuntu 快捷键使用说明(一)--截图
  9. 【Mac 环境配置】--安装git及使用
  10. linux xargs