问题的出发点

在网上看到一道题:

  1. 1
  1. String str = new String("abc");

以上代码执行过程中生成了多少个 String 对象?

答案写的是两个。”abc”本身是一个,而 new 又生成了一个。

“abc”是什么

查看这句程序的字节码,如下:

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  1. NEW String
  2. DUP
  3. LDC "abc"
  4. INVOKESPECIAL String.<init>(String) : void
  5. ASTORE 1

指令ldc indexbyte的含义:将两字节的值从 indexbyte 索引的常量池中的项中推到方法栈上。

指令LDC "abc"说明了”abc”并不是直接以对象存在的,而是存在于常量池的索引中。String 的构造函数调用命令实际使用的就是 String 类型作为参数,那么,栈上应该有一个 String 类型的索引。

由此我们得出,在字节码中,ldc 命令在常量池中找到了能索引到“abc”那个 String 对象的索引值。

常量池

常量池是类文件(.class)文件中的一部分,记录了许多常量信息,索引的字符串信息。

由于 Java 是动态加载的,类文件并没有包含程序运行时的内存布局,方法调用等无法直接记录出方法的物理位置,常量池通过索引的方法解决了这个问题。

常量池中存着许多表,其中 Constant_Utf8_info 表中,记录着会被初始化为 String 对象的字符串的字面值(iteral)。
而在 String 的 java doc 中,有对 String 字面值的说明:

All string literals in Java programs, such as “abc”, are implemented as instances of this class.

在 Java 编译的过程中,确定下来的 String 字面值都先被优化记录在常量池中(那些双引号字符串,都是以 CONSTANT_utf8_info 的形式存储在常量池中的)。也就是说,Java 源代码文件中出现的那些诸如”abc”字符串,都已经被提前放在了常量池中。

可以使用如下代码验证这一点:

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  1. public class Program
  2. {
  3. public static void main(String[] args)
  4. {
  5. String str1 = "Hello";
  6. String str2 = "Hello";
  7. System.out.print(str1 == str2);
  8. }
  9. }

输出结果是 true.说明”Hello”作为对象是被程序从同一个内存空间读取出来的。

常量池是编译时产生的,存在于类文件中(*.class 文件)。运行时,JVM 中每个对象都拥有自己的运行时常量池(run time constant pool)。

字符串池

我在 String 的 java doc 中又发现了一个有趣的 method:intern() ,我翻译如下:

当 intern 方法被调用,如果池中已经拥有一个与该 String 的字符串值相等(即 equals()调用后为 true)的 String 对象时,那么池中的那个 String 对象会被返回。否则,池中会增加这个对象,并返回当前这个 String 对象。

其中有介绍一个字符串池的东西:字符串池(String pool),初始是空的,由类私有的控制。

查看 java.lang.String 的源代码,发现 Intern()方法是一个 native 方法,即本地实现的方法,而不是一个 java 方法,这让我们不能直观的看到字符串池的实现细节。不过能够理解字符串池其实是类似于线程池的缓冲器,可以起到节约内存的作用。如下代码可以验证

  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  1. package biaobiaoqi.thinkingInJava;
  2. public class Test {
  3. public static void main(String[] args){
  4. String strA1 = "ab";
  5. String strA2 = "c";
  6. String strB1 = "a";
  7. String strB2 = "bc" ;
  8. System.out.println((strA1+strA2).intern() == (strB1 + strB2).intern());
  9. }
  10. }

输出结果为 true。

现代的 JVM 实现里,考虑到垃圾回收(Garbage Collection)的方便,将 heap 划分为三部分: young generation 、 tenured generation(old generation)和 permanent generation( permgen )

字符串池是为了解决字符串重复的问题,生命周期长,它存在于 permgen 中。

总结

编译 Java 源代码时,源文件中出现的双引号内的字符串都被收纳到常量池中,用 CONSTANT_utf8_info 项存储着。

JVM 中,相应的类被加载运行后,常量池对应的映射到 JVM 的运行时常量池中。其中每项 CONSTANT_utf8_info(也就试记录那些字符串的)都会在常量引用解析时,自动生成相应的 internal String,记录在字符串池中。

回过头来看看文章刚开始的那个问题。

  1. 1
  1. String str = new String("abc");

这里确实是有两个 String 对象生成了。

new String("xxx") 创建的 String 对象会在 heap 中重新生成新的 String 对象,绕过字符串池的管辖。而如果使用String str = "xxx"则先查看字符串池 是否已经存在,存在则直接返回 PermGen 中的该 String 对象,否则生成新的 String 对象,并将它加入字符串池中。

尽量使用String str = "abc";,而不是String str = new String("abc");用 new 的方法肯定会开辟新的 heap 空间,而前者的方法,则会通过 string interning 优化。

参考资料

 原文地址:http://biaobiaoqi.github.com/blog/2013/09/08/string-interning/
 版权声明:自由转载-非商用-非衍生-保持署名| Creative Commons BY-NC-ND 3.0

对Java字符串的探究的更多相关文章

  1. (转)Java动态追踪技术探究

    背景:美团的技术沙龙分享的文章都还是很不错的,通俗易懂,开阔视野,后面又机会要好好实践一番. Java动态追踪技术探究 楔子 jsp的修改 重新加载不需要重启servlet.如何在不重启jvm的情况下 ...

  2. Java动态追踪技术探究(动态修改)

    Java动态追踪技术探究 Java探针-Java Agent技术-阿里面试题 秒懂Java动态编程(Javassist研究) 可以用于在类加载的时候,修改字节码. Java agent(Java探针) ...

  3. Java 字符串格式化详解

    Java 字符串格式化详解 版权声明:本文为博主原创文章,未经博主允许不得转载. 微博:厉圣杰 文中如有纰漏,欢迎大家留言指出. 在 Java 的 String 类中,可以使用 format() 方法 ...

  4. (转)Java字符串

    转自:http://blog.sina.com.cn/s/blog_899678b90101brz0.html 创建字符串有两种方式:两种内存区域(字符串池,堆)1," " 引号创 ...

  5. Java字符串split函数的注意事项

    Java字符串的split方法可以分割字符串,但和其他语言不太一样,split方法的参数不是单个字符,而是正则表达式,如果输入了竖线(|)这样的字符作为分割字符串,会出现意想不到的结果, 如, Str ...

  6. 关于java字符串编译优化问题

    情景一:不好的字符串拼接习惯    起因是这样的:一个大牛在写了一篇关于java字符串优化问题的讲解,他提到:不要使用strObj+otherValue的方法将otherValue转换为字符串形式,因 ...

  7. Java字符串排列算法

    Java字符串排列算法 题目:现有ABCDE 5个球 构成的排列组合 可重复抽取 最多取到16个 共有多少种组合方式? 比如:取1个球可以构成的组合有 A B C D E 共5种,取2个球可以构成的组 ...

  8. Java字符串转换

    public class StringConvertToInt{ public static void main(String[] args) { String a ="12a34bW()5 ...

  9. Java字符串null相加

    Java字符串null相加 最近和同事讨论了下面的一段代码: String a = null; a += a; System.out.println(a); 运行结果: nullnull 本着学习的态 ...

随机推荐

  1. jmeter--基本组件介绍

    一.JMeter 介绍 Apache JMeter是100%纯Java桌面应用程序,被设计为用于测试客户端/服务端结构的软件(例如web应用程序).它可以用来测试静态和动态资源的性能,例如:静态文件, ...

  2. 使用前端后台管理模板库admin-lte(转)

    使用前端后台管理模板库admin-lte 使用前端后台管理模板库admin-lte 安装 搭建环境 安装 安装admin-lte,可以通过以下几种办法安装,下图是GitHub中admin-lte的主页 ...

  3. AE中Identify查询工具的实现

    原文 AE中Identify查询工具的实现 主要实现点击查询并闪烁显示,并把查询要素的信息通过DataGridView显示出来,主要用到的接口: IIdentity.IArray.IIdentifyO ...

  4. Android 利用an框架快速实现网络请求(含下载上传文件)

    作者:Bgwan链接:https://zhuanlan.zhihu.com/p/22573081来源:知乎著作权归作者所有.商业转载请联系作者获得授权,非商业转载请注明出处. an框架的网络框架是完全 ...

  5. 使用RxPermissions(基于RxJava2)

    使用RxPermissions(基于RxJava2) 0. 背景 Android 6.0 (API level 23)中.将权限分成了两类.一类是Install权限(称之为安装时权限).还有一类是Ru ...

  6. javascript怎么获取指定url网页中的内容

    javascript怎么获取指定url网页中的内容 一.总结 一句话总结:推荐jquery中ajax,简单方便. 1.js能跨域操作么? javascript出于安全机制不允许跨域操作的. 二.用ph ...

  7. angular表单经验分享

    原文 https://www.jianshu.com/p/af359bd04f32 大纲 1.表单的名字不可以和传入的值的名字相同 2.在模板驱动表单的时候要使用angular表单的验证功能,需要将n ...

  8. STL algorithm算法make_heap和sort_heap(32)

    make_heap原型: std::make_heap default (1) template <class RandomAccessIterator> void make_heap ( ...

  9. 批量杀死MySQL连接的几种方法

    法一: 通过information_schema.processlist表中的连接信息生成需要处理掉的MySQL连接的语句临时文件,然后执行临时文件中生成的指令.   mysql> select ...

  10. Seagate-保修验证(za25shrx)

    保修验证 http://support.seagate.com/customer/zh-CN/warranty_validation.jsp   Seagate   保修验证    End User  ...