小学徒进阶系列—JVM对String的处理

 对于String类型,java官网的文档是这样子描述的: html

  http://bbs.itheima.com/thread-23776-1-1.html?fstgjString类表明着字符串。java程序中的全部字符串字面值(如"abc")都做为此类的实例实现。 java

  字符串是常量,他们的值在建立以后不能更改。由于 String 对象是不可变的,因此能够共享。 缓存

  那么,jvm是怎么共享这些字符串的呢? app

  为了节省内存,提升资源的复用,jvm引入了常量池这个概念,它属于方法区的一部分的,做用之一就是存放编译期间生产的各类字面量和符号引用。而从前面的博文《深刻了解JVM—内存区域》咱们能够知道,方法区的垃圾回收行为是比较少出现的,该区中的对象基本不会被回收,能够理解成是永久存在的。 jvm

  所以,缓存在字符串缓冲区中的字符串对象基本是不被回收的,而jvm也正是经过复用这些对象从而达到共享做用。 学习

  从上一段话中的概念能够知道,通常状况下,只有编译期间能够肯定下来的的字符串才能存放到缓冲区中。为何要强调是通常状况下呢?由于String类为咱们提供了一个intern()方法,它能够帮咱们将不存在于缓存池中的java字符串添加到缓存池中,并返回缓存池中该字符串对象的引用。 ui

  具体关于intern()方法,后面咱们再给出代码作简单说明吧。如今咱们将重点放在,什么状况下可以在编译期间直接肯定字符串变量值而且将它添加到缓冲区中呢? spa

  若是程序的字符串链接表达式中没有使用变量或者调用方法,那么该字符串变量的值就可以在编译期间肯定下来,而且将该字符换缓存在缓冲区中,同时让该变量指向该字符串;不然将没法利用缓冲区,由于使用了变量和调用了方法以后的字符串变量的值只能在运行期间才能肯定链接式的值,也就没法在编译期间肯定字符串变量的值,从而没法将字符串变量增长到缓冲区并加以利用。 code

下面咱们来看看如何代码并经过查看他的编译过程来验证上述结论吧。 htm

代码一(没有使用变量或者调用方法):

复制代码
 1 package com.xiaoxuetu.string;  2  3 public class Test {  4  5 public static void main(String args[]) {  6 String param1 = "abc";  7 String param2 = "abc" + "def";  8 String param3 = "abcdef";  9  } 10 }
复制代码

  首先咱们打开cmd.exe, 经过javac Application.java编译好该Java文件,而后经过命令javap -c Application来查看java编译后的ByteCode字节码,如图所示:

  咱们先来解释下,ldc的含义是:将常量值从常量池中取出来而且压入栈中。从上图中,咱们能够看到第0行、第3行和第6行中,程序分别从常量池中取出 "abc" 和 "abcdef" 而且压入栈中,并且,第3行和第6行中的字符串引用是同一个。这说明了,在编译期间,该字符串变量的值已经肯定了下来,而且将该字符串值缓存在缓冲区中,同时让该变量指向该字符串值,后面若是有使用相同的字符串值,则继续指向同一个字符串值。

  若是有安装jad的话,咱们还能够经过jad -o -a Test.class命令来生成java代码和对应java编译后的ByteCode字节码一块儿的jad文件,如图所示:

 

代码二(使用了变量或者调用了方法):

复制代码
1 package com.xiaoxuetu.string; 2 3 public class Application { 4 public static void main(String[] args) { 5 String param = "abc"; 6 String param1 = "3abc"; 7 String param2 = param.length() + "abc"; 8  } 9 }
复制代码

  一样,咱们编译后用jad命令来生成对应的文件查看比较方便吧。

  从上图中,咱们看到了param的值引用是从常量池中取出的字符串"abc", param1的引用也是直接从常量池中取出的"3abc";可是param2的值并无根据运算结果引用常量池中的“3abc”,而是返回当前StringBuilder对象的引用。这点咱们能够直接经过 param2 == param1 来判断,很明显输出结果就是false.

  除此以外,咱们还能够从该图中的第11行到第19行看出,javas在处理str = str.length() + "def"的时候,是经过StringBuilder实例对象的append()方法来实现的。返回的是StringBuilder对象的引用,因此此时str的值并无引用常量池中缓存的也有的对象。对此,官网文档是这么解释的:

  Java 语言提供对字符串串联符号("+")以及将其余对象转换为字符串的特殊支持。字符串串联是经过 StringBuilder(或 StringBuffer)类及其 append 方法实现的。字符串转换是经过 toString 方法实现的,该方法由 Object 类定义,并可被 Java 中的全部类继承。有关字符串串联和转换的更多信息,请参阅 Gosling、Joy 和 Steele 合著的 The Java Language Specification。

 

或许有人看了之后会有如下疑问:

1> 若是将前面代码二中的第6 、7行交换,变成以下:

1 String param2 = param.length() + "abc"; 2 String param1 = "3abc";

那么param2变量的值“3abc”会不会缓存,而后被param1直接取出来使用呢?

答案是不会的,由于param2变量的字符串值必须在运行时才能肯定下来,而不是概念中编译期间,真正将"3abc"缓存的反而会是param1这行代码。

 

2>若是咱们经过String newStr = new String("abc");来建立字符串变量,那么abc会不会被缓存呢?并且会不会直接指向缓冲区中的变量呢?

好吧,咱们继续看看代码而后经过查看编译消息进行分析:

代码三(使用了变量或者调用了方法):

复制代码
 1 package com.xiaoxuetu.string;  2  3 public class Application2 {  4 public static void main(String[] args) {  5 String param = "abc";  6 String newStr = new String("cde");  7 String param2 = "cde";  8  9  } 10 }
复制代码

接着查看jad命令执行后生成的文件:

咱们看到在建立newStr的String类型对象的时候,先从栈中取出字符串"cde",而后调用String的构造方法经过关键字new 进行建立对象的建立,将新的引用赋给newStr。所以newStr并无指向缓冲区中的字符串“cde”,因此经过这种方法建立的字符串变量开销每每比较大。

接下来咱们讲解一下intern()方法吧。关于这个方法,官网是这么描述的:

当调用 intern 方法时,若是池已经包含一个等于此 String 对象的字符串(用 equals(Object) 方法肯定),则返回池中的字符串。不然,将此 String 对象添加到池中,并返回此 String 对象的引用。

下面咱们只给出一个intern()方法使用的例子,具体你们就自行研究咯。

  View Code

 

文笔表达能力有限,可能写的比较通常。不知道你们看了以后会不会有其余问题哦,但愿你们踊跃提出,共同窗习共同进步。谢谢。

最后就总结一下判断字符串是否被缓存到缓冲区的两大要素 :

1>编译期间 : 也就说字符串链接式中没有使用变量或者调用方法。

2>是否使用了intern()方法 : 使用了该方法的字符串变量的值若是不存在缓冲区中将会被缓存。

 

 

 

转载请注明出处:http://www.cnblogs.com/xiaoxuetu/  ,谢谢合做

 哈喽, 你们好! 我是小学徒V。  您的支持是我无限的动力,在此很是感谢您阅读完本篇文章。

 若是你们以为我写的不错的话,不要忘记动动手指点下左下角的  好文要顶  按钮哦

 若是你们想继续关注个人后续博文,能够经过直接点击左下角的  关注我  按钮关注个人最新动态

 若是你们对本文内容存在疑问,能够直接留下评论,我会及时处理的哦

相关文章
相关标签/搜索