常量池 VS 运行时常量池 VS 字符串常量池

目录

一.前言

二.常量池[ class文件常量池 , 静态常量 ]

三.运行时常量池

四.字符串常量池

五、String 解析--建立对象存储分析

一、String s = "abc"; 

二、String s = new String("abc");


一.前言

最近在看JVM, 常量池, 运行时常量池,字符串常量池 这个看的有点懵. 整理一下. 

class常量池 是在编译的时候每个class都有的. 在编译阶段,存放的是常量的 符号引用 。       [在class文件中.]             
字符串常量池 在每个VM中只有一份,存放的是字符串常量的 引用值 。  [在堆中.]                                       
运行时常量池 是在类加载完成之后,将每个class常量池 中的符号引用值转存到运行时常量池中,也就是说,每个class都有一个 运行时常量池 ,类在 解析阶段 ,将 符号引用 替换成 直接引用 ,与字符串常量池 中的引用值保持一致[在方法区.]    

关系图如下:

原图地址: https://www.processon.com/view/5f45d1d9e0b34d638e07816d

二.常量池[ class文件常量池 , 静态常量 ]

class文件常量池是指编译生成的class字节码文件结构中的一个常量池(Constant Pool Table),用于存放编译期间生成的各种字面量和符号引用,是在class文件中存放的.  这部分内容将在类加载后,存放于方法区的运行时常量池。

常量池中主要存放两大类常量: 字面量(Literal) 和符号引用(SymbolicReferences)  

字面量比较接近于Java语言层面的常量概念, 如文本字符串、 被声明为final的常量值等。

而符号引用则属于编译原理方面的概念.

主要包括下面几类常量:

·被模块导出或者开放的包(Package)
·类和接口的全限定名(Fully Qualified Name)
·字段的名称和描述符(Descriptor)
·方法的名称和描述符
·方法句柄和方法类型(Method Handle、 Method Type、 Invoke Dynamic)
·动态调用点和动态常量(Dynamically-Computed Call Site、 Dynamically-Computed
Constant)

三.运行时常量池

jvm在执行某个类的时候,必须经过加载、连接、初始化,而连接又包括验证、准备、解析三个阶段。而当类加载到内存中后,jvm就会将 class常量池 中的内容存放到 运行时常量池 中.

由此可知,运行时常量池 也是每个类都有一个

class常量池 中存的是字面量和符号引用,也就是说他们存的并不是对象的实例,而是对象的符号引用值。而经过解析(resolve)之后,也就是把符号引用替换为直接引用,解析的过程会去查询 字符串常量池 ,也就是我们上面所说的StringTable,以保证 运行时常量池所 引用的字符串与 字符串常量池 中所引用的是一致的。

四.字符串常量池

字符串常量池是全局的,JVM中独此一份,因此也称为全局字符串常量池。运行时常量池中的字符串字面量若是成员的,则在类加载初始化阶段就使用到了字符串常量池;若是本地的,则在使用到的时候才会使用字符串常量池。其实,“使用常量池”对应的字节码是一个ldc指令,在给String类型的引用赋值的时候会先执行这个指令,看常量池中是否存在这个字符串对象的引用,若有就直接返回这个引用,若没有,就在堆里创建这个字符串对象并在字符串常量池中记录下这个引用。

String类的intern()方法还可以在运行期间把字符串放到字符串常量池中。

String.intern()

我们可以使用 String.intern 方法来操作字符串常量池。

该方法用来返回常量池中的某个字符串

简单来说该功能是利用 Hotspot 虚拟机里的一个 StringTable 来存储字符串,StringTable 底层使用 HashTable 来实现的。如果 StringTable 中已经存在该字符串(通过 String.equals 方法来判断是否存在),则直接返回常量池中该对象的引用。否则,在 StringTable 中加入该对象,然后返回对象的引用。

public class ConstantPool {
 
    public static void main(String []args) {
 
        // 1. 在堆中会有一个 abc 实例对象,全局 StringTable 中存放着 abc 的一个引用值。
        String str1 = "abc";
 
        // 2. 会生成两个实例,一个是 def 的实例对象,
        //    并且 StringTable 中存储一个 def 的引用值,
        //    还有一个是new出来的一个 def 的实例对象 。
        String str2 = new String("def");
 
 
        // 3. 当在解析str3的时候查找StringTable,
        //    里面有 abc 的全局驻留字符串引用,
        //    所以str3的引用地址与之前的那个已存在的相同 。
        String str3 = "abc";
 
 
        // 4. str4是在运行的时候调用 intern() 函数,
        //    返回StringTable中 def 的引用值,
        //    如果没有就将str2的引用值添加进去,
        //    在这里,StringTable中已经有了 def 的引用值了,
        //    所以返回上面在new str2的时候添加到StringTable中的 def 引用值.
        String str4 = str2.intern();
 
        // str5在解析的时候就也是指向存在于StringTable中的 def 的引用值。
        String str5 = "def";
 
        String str6 = str1.intern();
 
 
 
        // str1 str3 在字符串常量池中是同一个引用,所以是true
        System.out.println(str1 == str3); //true
 
        System.out.println(str4);
 
        // str2 对象 的引用和 str4的引用不是同一个引用地址.
        // str2 会产生两个地址, 这里的str2 指向的是new的对象的地址, 和str4里面指向的def地址不同.
        // str4 引用的是str2在new 对象之前已经向常量池中放入的 def 地址 的引用.
        System.out.println(str2 == str4); //false
 
        // str4 str5 在字符串常量池中是同一个引用,所以是true
        System.out.println(str4 == str5); //true
 
 
        System.out.println("-------------");
        //刷新str4的引用为def的引用
        str4 = "def";
 
        // str2 对象 的引用和 str4的引用不是同一个引用地址.
        // str2 会产生两个地址, 这里的str2 指向的是new的对象的地址, 和str4里面指向的def地址不同.
        // str4 引用的是str2在new 对象之前已经向常量池中放入的 def 地址 的引用.
        System.out.println(str2 == str4); //false
 
        // str2 的引用和 str4的引用不是同一个引用地址.
        // str4 引用的是str5的引用.
        System.out.println(str4 == str5); //true
 
        // str1 str5 在字符串常量池中是同一个引用,所以是true
        System.out.println(str1 == str6);
 
        //str6会直接引用 字符串常量池中的引用,这个引用和str5的引用是一样的
        str6 = "def";
        // str6 str5 在字符串常量池中是同一个引用,所以是true
        System.out.println(str5 == str6);
    }
}

VM中除了字符串常量池,8种基本数据类型中除了两种浮点类型外,其它的6种基本数据类型的包装类都使用了缓冲池,但是Byte、Short、Integer、Long、Character这几个包装类只有对应值在[-128,127]时才会使用缓冲池,超出此范围仍会去创建新的对象。

五、String 解析--建立对象存储分析

一些数据。JVM虚拟机为每一个被装载的类型维护一个常量池。常量池就是该类型所用到常量的一个有序集和,包括直接常量(String,Integer和 Floating point常量)和对其余类型,字段和方法的符号引用

对于String常量,它的值是在常量池中的。而JVM中的常量池在内存当中是以表的形式存在的, 对于String类型,有一张固定长度的CONSTANT_String_info表用来存储文字字符串值,注意:该表只存储文字字符串值,不存储符号引用。 

一、String s = "abc"; 

建立过程分析:在class文件被JVM装载到内存中,JVM会建立一块String Pool(String缓冲池)。当执行String s = “abc”;时,JVM首先在String Pool中查看是否存在字符串对象“abc”(如何查看呢?用equals()方法判断),若是已存在该对象,则不用建立新的字符串对象“abc”,而直接使用String Pool中已存在的对象“abc”,而后将引用s指向该对象;若是不存在该对象,则先在String Pool中建立一个新的字符串对象“abc”,而后将引用s指向String Pool中建立的新对象。 

注意:使用“字符串常量”引号建立的字符串对象时,在编译期就已经肯定将该对象存储到String Pool中了。所以,String s = “abc”只会在编译期,在String Pool中建立一个对象。  

例如: 

1 String s1 = "abc";    
2 String s2 = "abc";    
3 System.out.println(s1 == s2);//true    

结果说明:JVM建立了两个引用str1和str2,但在String Pool中只建立了一个对象,并且两个引用都指向了同一个对象。 

二、String s = new String("abc");

建立过程分析:当执行String s = new String(“abc”);时,JVM首先在String Pool中查看是否存在字符串对象“abc”,若是不存在该对象,则先在String Pool中建立一个新的字符串对象“abc”,而后执行new String(“abc”)构造方法,在Heap里又建立一个新的字符串对象“abc”(new出来的对象都放在Heap里面),并将引用s指向Heap中建立的新对象;若是已存在该对象,则不用建立新的字符串对象“abc”,而直接使用String Pool中已存在的对象“abc”, 而后执行new String(“abc”)构造方法,在Heap里又建立一个新的字符串对象“abc”,并将引用s指向Heap中建立的新对象。 

注意:使用new String(“”)建立的字符串对象时,会在运行期建立新对象存储到Heap中。

所以,new String(“abc”)建立字符串对象时,会建立2个对象,编译期在String Pool中建立一个,运行时Heap中建立一个。 

这里使用了

  1. * Initializes a newly created String object so that it
         * represents the same sequence of characters as the argument; in other
         * words, the newly created string is a copy of the argument string. Unless 
         * an explicit copy of original is needed, use of this 
         * constructor is unnecessary since Strings are immutable.
  2. public String(String original)  

翻译以下:这个构造方法来用来初始化新建立的String对象,使之具备与参数有相同的字符串顺序流。换句话说,新建立的字符串对象只是一个参数的拷贝。由于String对象是不可改变的,因此除非有必要显式地生成参数的副本,不然不必用此构造方法来构造String对象。

这个构造方法,做用:初始化一个新建立的String对象,使其表示一个与参数相同的字符序列;换句话说,新建立的字符串是该参数字符串的副本。 
例如: 

1 String s1 = new String("abc");  
2 String s2 = new String("abc");  
3 System.out.println(s1 == s2);//false  

结果说明:只要是用new()来新建对象的,都会在堆(Heap)中建立,并且其字符串是单独存值的,即便与String Pool中的数据相同,也不会与String Pool中的数据共享。 

例程1: 

 1 String s1 = "abcdef";  
 2 String s2 = "abcdef";  
 3 String s3 = "abc"+"def";//编译期自动优化为String s3 = "abcdef";  
 4 System.out.println(s1 == s2);  
 5 System.out.println(s1 == s3);  
 6 System.out.println(s2 == s3);  
 7 
 8 运行结果: 
 9 true 
10 true 
11 true 

结果说明:字符串常量生成的字符串对象在String Pool中只有一个拷贝,且它是在编译期就被肯定了,因此“s1 == s2”;“abc”和“def”都是字符串常量,当一个字符串由多个字符串常量链接而成时,它本身也确定是字符串常量(它在编译期就被解析为一个字符串对象了,即class文件中就已经存在“abcdef”),因此在字符串生成字符串对象时,s3也是String Pool中“abcdef”的一个引用。故JVM对于字符串常量的"+"号链接,在程序编译期,JVM就将常量字符串的"+"链接优化为链接后的值。 

例程2: 

 1 String s1 = "abc";  
 2 String s2 = "def";  
 3 String s3 = "abcdef";  
 4 String s4 = "abc"+"def";  
 5 String s5 = s1 + "def";  
 6 String s6 = "abc"+s2;  
 7 String s7 = s1 + s2;  
 8 System.out.println(s3 == s4);  
 9 System.out.println(s3 == s5);  
10 System.out.println(s3 == s6);  
11 System.out.println(s3 == s7);  
12 
13 运行结果以下: 
14 true 
15 false 
16 false 
17 false 

结果说明:JVM对于有字符串引用存在的字符串"+"链接中,而引用的值在程序编译期是没法肯定的,即s1 + “def”没法被编译器优化,只有在程序运行期来动态分配并将链接后的新地址赋给s5。 

例程3: 

 1 final String s1 = "abc";  
 2 String s2 = "def";  
 3 String s3 = "abcdef";  
 4 String s4 = "abc"+"def";  
 5 String s5 = s1 + "def";  
 6 String s6 = "abc"+s2;  
 7 String s7 = s1 + s2;  
 8 System.out.println(s3 == s4);  
 9 System.out.println(s3 == s5);  
10 System.out.println(s3 == s6);  
11 System.out.println(s3 == s7);  
12 
13 运行结果以下: 
14 true 
15 true 
16 false 
17 false 

例程4: 

 1 final String s1 = "abc";  
 2 final String s2 = "def";  
 3 String s3 = "abcdef";  
 4 String s4 = "abc"+"def";  
 5 String s5 = s1 + "def";  
 6 String s6 = "abc"+s2;  
 7 String s7 = s1 + s2;  
 8 System.out.println(s3 == s4);  
 9 System.out.println(s3 == s5);  
10 System.out.println(s3 == s6);  
11 System.out.println(s3 == s7);  
12 
13 运行结果以下: 
14 true 
15 true 
16 true 
17 true 

结果说明:例程3和例程4与例程2的区别是,例程3在字符串s1前加了final修饰,例程4在字符串s1和s2前都加了final修饰。

对于final修饰的变量,它在编译时被解析为常量值的一个本地拷贝存储到本身的常量池中或嵌入到它的字节码流中

因此此时的s1 + “def”和"abc" + "def"效果是同样的。接着后面两个含引用的字符串链接,JVM会进行相同的处理。故上面程序后面三个的结果为true。 

例程5: 

 1 public static void main(String args[]){  
 2     String s1 = "abc";  
 3     final String s2 = getDef();  
 4     String s3 = "abcdef";  
 5     String s4 = "abc"+s2;  
 6     String s5 = s1 + s2;  
 7     System.out.println(s3 == s4);  
 8     System.out.println(s3 == s5);  
 9 }  
10 private static String getDef(){  
11     return "def";  
12 }  
13 
14 程序运行结果以下: 
15 false 
16 false 

结果说明:JVM对于方法调用给字符串引用赋值的状况,引用指向字符串的值在编译期是没法肯定的,只有在程序运行调用方法后,将方法的返回值“def”和“abc”动态链接并分配新地址赋值给s4,因此上述程序的结果都为false。 

经过以上的例子可知: 

String s = "a" + "b" + "c";  

等价于:
String s = "abc";  

编译期,直接优化,进行常量链接。 

对于: 
String a = "a";  
String b = "b";  
String c = "c";  
String s = a + b + c;  

就不等价于:
String s = "abc";  

最终结果等于: 
StringBuilder builder = new StringBuilder ();  
builder.append(a);  
builder.append(b);  
builder.append(c);  
String s = builder.toString();  

去看StringBuilder的toString()方法: 

  1. public String toString() {  
  2. return new String(value, 0, count);  
  3. }  

能够发现是经过new String(..)返回了一个String对象,也就是说在堆中建立了对象。这时候会不会在池中出现"abc"这个对象呢?(question还没解决) 

生成String s的过程当中,编译器使用sb执行的过程:建立一个StringBuffer对象,使用append()向此StringBuffer对象直接添加新的字符串(而不是每次制做一个新的副本)。 

对于String c = "c";String s = "a" + "b" + c;,编译器将会先将"a" + "b"做为编译时常量,优化生成成字面常量"ab" ,而后生成一个StringBuilder对象,接着调用两次 append()方法,即: 
String s = new Builder().append("ab").append(c) .toString(); 

对于String a = "a";String s = a + "b" + "c";,编译器分析a为引用变量,后面的"b" + "c"就不会做为编译时常量来运算了。至关于执行: 
String s = new Builder().append(a).append("b") .append("c") .toString(); 

对于String b = "b";String s = "a" + b + "c";],这种形式的就没办法优化了,直接生成StringBuilder对象,而后调用三次 append()方法,即: 
String s = new Builder().append("a").append(b) .append("c") .toString(); 

接着,咱们再看如下代码: 

1 String str1 = "abc";//是字符串常量,它在编译期被肯定,放在常量池中(共享内容值)  
2 //new String()建立的字符串不放入常量池中  
3 String str2 =new String("abc");//不是字符串常量,不在编译期肯定(不共享内容值)  
4 String str1 = new String("abc");  
5 String str2 = "abc";  
6 System.out.println(str1==str2);  //false  

建立了两个引用。建立了两个对象。两个引用分别指向不一样的两个对象。 

  1. String str1 = "abc";  
  2. String str2 = new String("abc");  
  3. System.out.println(str1==str2);  //false  


建立了两个引用。建立了两个对象。两个引用分别指向不一样的两个对象。 

接下来咱们再来看看intern()方法,它的定义以下: 

  1. public native String intern();   

这是一个本地方法。在调用这个方法时,JAVA虚拟机首先检查String Pool中是否已经存在与该对象值相等对象存在,若是有则返回字符串池中对象的引用;若是没有,则先在String Pool中建立一个相同值的String对象,而后再将它的引用返回。 

例程6: 

 1 public class TestString{    
 2     public static void main(String args[]){    
 3         String s1 = new String("abc");//语句1    
 4         String s2 = "abc";//语句2    
 5         String s3 = new String("abc");//语句3    
 6     
 7         System.out.println(s1 == s2);//语句4    
 8         System.out.println(s1 == s3);//语句5    
 9         System.out.println(s2 == s3);//语句6    
10     
11         System.out.println(s1 == s1.intern());//语句7    
12         System.out.println(s2 == s2.intern());//语句8    
13         System.out.println(s1.intern() == s2.intern());//语句9    
14     
15         String hello = "hello";//语句10    
16         String hel = "hel";//语句11    
17         String lo = "lo";//语句12    
18     
19         System.out.println(hello == "hello");//语句13    
20         System.out.println(hello == "hel" + "lo");//语句14    
21         System.out.println(hello == "hel" + lo);//语句15    
22         System.out.println(hello == hel + lo);//语句16    
23     }    
24 }    

问题1:当执行完语句(1)时,在内存里面生成几个对象?它们是什么?在什么地方? 
--->当执行完语句(1)时,在内存里面建立了两个对象,它们的内容分别都是abc,分别在String Pool(常量池)和Heap(堆)里。其字符串的建立过程以下:首先在String Pool里面查找查找是否有 "abc",若是有就直接使用,但这是本程序的第一条语句,故不存在一个对象"abc",因此要在String Pool中生成一个对象"abc",接下来,执行new String("abc")构造方法,new出来的对象都放在Heap里面。在Heap里又建立了一个"abc"的对象。这时内存里就有两个对象了,一个在String Pool 里面,一个在Heap里面。 

问题2:当执行完语句(2)时,在内存里面一共有几个对象?它们是什么?在什么地方? 
当执行完语句(2)时,在内存里面一个对象也没有建立。当咱们定义语句(2)的时候,若是咱们用字符串的常量值(字面值)给s2赋值的话,那么首先JVM仍是从String Pool里面去查找有没有内容为abc的这样一个对象存在,咱们发现当咱们执行完语句(1)的时候,StringPool里面已经存在了内容为abc的对象,那么就不会再在String Pool里面去生成内容为abc的字符串对象了。而是会使用已经存在String Pool里面的内容为abc的字符串对象,而且会将s2这个引用指向String Pool里面的内容为abc的字符串对象,s2存放的是String Pool里面的内容为abc的字符串对像的地址。也就是说当你使用String s2 = "abc",即便用字符串常量("abc")给定义的引用(str2)赋值的话,那么它首先是在String Pool里面去找有没有内容为abc的字符串对象存在,若是有的话,就不用建立新的对象,直接引用String Pool里面已经存在的对象;若是没有的话,就在 String Pool里面去建立一个新的对象,接着将引用指向这个新建立的对象。因此,当执行完语句(2)时内存里面一共有2个对象,它们的内容分别都是abc,在String Pool里面一个内容abc的对象,在Heap里面有一个内容为abc的对象。 

问题3:当执行完语句(3)时,在内存里面一共有几个对象?它们是什么?在什么地方? 
当执行完语句(3)时,其执行过程是这样的:它首先在String Pool里面去查找有没有内容为abc的字符串对象存在,发现有这个对象存在,它就不去建立 一个新的对象。接着执行new...,只要在java里面有关键字new存在,无论内容是否相同,都表示它将生成一个新的对象,new多少次,就生成多少个对象,并且新生成的对象都是在Heap里面,因此它会在Heap里面生成一个内容为abc的对象,而且将它的地址赋给了引用s3,s3就指向刚在Heap里面生成的内容为abc的对象。因此,当执行完语句(3)时,内存里面一共有3个对象,其中包含了在String Pool里面一个内容为abc的字符串对象和在Heap里面包含了两个内容为abc的字符串对象。 

问题4:当执行完语句(4)(5)(6)后,它们的结果分别是什么? 
在java里面,对象用"=="永远比较的是两个对象的内存地址,换句话说,是比较"=="左右两边的两个引用是否指向同一个对象。对于java里面的8种原生数据类型来讲,"=="比较的是它们的字面值是否是同样的;对应用类型来讲,比较的是它们的内存地址是否是同样的。在语句(1)(2)(3)中,因为s一、s二、s3指向不一样的对象,它们的内存地址就不同,所以能够说当执行完语句(4)(5)(6),它们返回的结果都是false。 

问题5:当执行完语句(7)(8)(9)后,它们的结果分别是什么? 
首先,s1这个对象指向的是堆中第一次new...生成的对象,当调用 intern 方法时,若是String Pool已经包含一个等于此 String 对象的字符串(该对象由equals(Object)方法肯定),则返回指向String Pool中的字符串对象的引用。由于String Pool中有内容为abc的对象,因此s1.intern()返回的是String Pool中的内容为abc的字符串对象的内存地址,而s1倒是指向Heap上内容为abc的字符串对象的引用。于是,两个引用指向的对象不一样,因此,s1 == s1.intern() 为false,即语句(7)结果为false。 
对于s2.intern(),它仍是会首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的地址赋给s2.intern()方法的返回值。由于s2和s2.intern()方法的返回值指向的是同一个对象,因此,s2 == s2.intern()的结果为true,,即语句(8)结果为true。 
对于s1.intern(),它首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的赋给s1.intern()方法的返回值。对于s2.intern(),首先检查String Pool中是否有内容为abc的对象,发现有,则将String Pool中内容为abc的对象的地址赋给s2.intern()方法的返回值。由于二者返回的地址都指向同一个对象,因此,s1.intern() == s2.intern()的结果为true,,便是语句(9)结果为true。 
所以,当执行完语句(7)(8)(9)后,它们的结果分别是false、true、true。 

问题6:当执行完语句(13)(14) (15)(16)后,它们的结果分别是什么? 
hello == "hello"引用hello指向的对象就是String Pool中的“hello”,即语句(13)的结果为true。 
hello == "hel" + "lo"当加号两边都是常量值时,就会组成一个新的常量值"hello"在String Pool里面,若是String Pool已经有相同内容的就不会再建立,则直接返回String Pool里面的内容为"hello"的字符串对象的内存地址,因此,hello == "hel" + "lo"结果为true。 
hello =="hel" + lo 当加号两边有一个不是常量值,会在堆里面建立一个新的"hello"对象,一个在String Pool中,一个在Heap中,故输出false 。 
hel + lo 同上,输出false。 
所以,当执行完语句(7)(8)(9)后,它们的结果分别是true、true、false、false。 

例程7: 

 1 String s1 = "abc";  
 2 String s2 = new String("abc");  
 3 String s3 = new String("abc");  
 4 s2.intern();//虽然执行了s2.intern(),但它的返回值没有赋给s2  
 5 s3 = s3.intern();//把String Pool中“abc”的引用赋给s3  
 6 System.out.println(s1 == s2);  
 7 System.out.println(s1 == s2.intern());    
 8 System.out.println(s1 == s3);  
 9 
10 运行结果以下: 
11 false 
12 true 
13 true 

结果说明:由于s2.intern()只是执行了,而没有把String Pool中的“abc”的地址赋给s2,因此s1仍是指向String Pool中的“abc”,s2则指向Heap中的“abc”,对于s1 == s2,返回值为false;对于s1 == s2.intern(),其中s2.intern()的返回值为指向String Pool中“abc”的地址,故s2.intern()与s1指向同一个对象,于是返回true;由于s3 = s3.intern()把String Pool中的“abc”的地址赋给了s3,此时s1和s3指向的是同一对象,即String Pool中的“abc“,于是返回true。 

例程8: 

 1 String s1 = "abc";  
 2 String s2 = new String("abc");  
 3 String s3 = "a" + new String("bc");  
 4 System.out.println(s1 == s2);  
 5 System.out.println(s1 == s3);  
 6 System.out.println(s2 == s3);  
 7 
 8 运行结果以下: 
 9 false 
10 false 
11 false 

结果说明: 
s1指向String Pool中的字符串对象“abc”,编译期肯定; 
s2指向Heap中的字符串对象“abc”,运行期肯定; 
s3指向Heap中的另外一个字符串对象“abc”,运行期肯定。 
注意:String s3 = "a" + new String("bc");等价于 
String s3 = new StringBuilder().append("a").append(new String("bc")).toString(); 

思考:String s = "a" + new String("b") + "c";产生了那几个对象? 
解:等价于String s = new StringBuilder().append("a").append(new String("b")).append("c").toString(),会在String Pool中产生"a"、"b"、"c"三个对象,在Heap中产生"b"、"abc"两个个对象。一共5个字符串对象。 

例程9: 

1 String s1 = "Hello";    
2 s1 = "Java";    
3 String s2 = "Hello";    
4 String s3 = new String("Hello");    
5 System.out.println(s1 == s2);    
6 System.out.println(s2 == s3);    
7 运行结果以下: 
8 false 
9 false 

分析这段程序的执行过程: 
首先在加载Java程序时,JVM会建立一片的内存空间(String Pool)专门存入string对象。 
String s1 = "Hello",如今栈中建立一个字符串引用s1,而后JVM会在String Pool中查找是否存在"Hello",若是存在,则直接使用它,将其地址赋给s1,若是不存在(这时String Pool中显然不存在"Hello"),则在String Pool中建立"Hello",并将其地址赋给s1。 

s1 = "Java",JVM会在String Pool中查找是否存在"Java",若是存在,则直接使用它,将其地址赋给s1,若是不存在(这时String Pool中显然不存在"Java"),则在String Pool中建立"Java",并将其地址赋给s1。而原来的字符串对象"Hello"仍然在String Pool中,没有消失,由于String对象的值是不能被修改的。这里只是改变了引用的值即引用指向的对象的地址,而没有改变它所引用的对象。 

String s2 = "Hello",JVM会在String Pool里查看有没有字符串"Hello",如有,则返回它的地址给s2,不然,建立新的String对象"Hello", 放到String Pool里。这里因为"Hello"对象已经建立,并存在于String Pool中,于是不须要从新建立String对象"Hello"。此时s1指向String Pool中的"Java",s2指向String Pool中的"Hello",故s1 == s2的值为false。 

String s3=String("Hello"),JVM会在String Pool里查看有没有字符串"Hello",如有,直接执行new操做,若没有,则先要在String Pool中建立"Hello",而后执行new操做,因为遇到了new,还会在Heap上(不是String Pool里)建立string对象"Hello",并将Heap上的"Hello"对象的地址赋给引用s3。因此s2 == s3将返回false,由于s2和s3不是引用同一个对象。 


原文链接:https://blog.csdn.net/zhanglong_4444/article/details/108235807

String 解析--建立对象存储分析 - 尚码园

别再问我 new 字符串创建了几个对象了!我来证明给你看!_vincent-CSDN博客_字符串创建了几个对象

深入理解Java中的String - 平凡希 - 博客园

https://blog.csdn.net/qq_41376740/article/details/80338158?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control&dist_request_id=&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-1.control

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值