为什么在堆中创建字符串对象和字符串文字的串联?

Yog*_*kar 8 java string

我有下面的字符串

String str1 = "Abc";//created in constant pool
String str2 = "XYZ";//created in constant pool
String str3 = str1 + str2;//created in constant pool
String str4 = new String("PQR");//created in heap
String str5 = str1.concat(str4);//created in heap 
String str6 = str1 + str4;//created in heap
Run Code Online (Sandbox Code Playgroud)

在这里,我不知道为什么将字符串(在常量池中创建,而另一个在堆中创建)的串联导致在堆中创建新字符串String。我不知道原因,为什么会发生?

Ste*_*n C 13

评论中有很多可疑的信息,因此我将给出一个正确的答案。

  1. 实际上,没有 “恒定池”之类的东西。您不会在Java语言规范中找到该术语。

    (也许您将术语与作为.class文件一部分的常量池和相应的每类运行时常量池相混淆,后者对应用程序不可见。这些是JVM定义的“规范工件”规范,用于定义字节码的执行模型。该规范并不需要物理存在,尽管它们通常存在;例如,在Oracle或OpenJDK实现中。

  2. 正在运行的JVM中有一个数据结构,称为字符串池。在JLS中未按名称提及字符串池,但是JLS指定的字符串文字属性暗含了它的存在。字符串池在javadocs和JVM规范中都有提及。

  3. 字符串池将包含String表示应用程序中使用的任何字符串值常量表达式的值的对象。这包括字符串文字。

  4. 字符串池一直以来一直字符串的重复数据删除机制。应用程序可以通过调用String.intern方法来使用此方法。

  5. 常量池中的字符串值(请参见上文)用于创建String应用程序看到的对象:

    • 根据String表示创建对象。
    • String.intern会被调用,并String从字符串池中返回相应的重复数据删除对象。
    • 该字符串成为“运行时常量池”类的一部分;即,类的运行时常量池将在字符串池中包含对String对象的引用。
    • 取决于Java实现,此过程可能急切地或懒惰地发生。
  6. 字符串池是并且一直存储在(或)堆中。

    • 在Java 7之前,字符串池中的字符串对象是在称为PermGen堆的特殊堆中分配的。在Java的最早版本中,它不是通过GC进行的。然后只是偶尔进行了GC处理。

    • 在Java 7中(不是8!),字符串池使用PermGen堆停止,而使用常规堆。

    • 在Java 8中,PermGen堆(出于某些目的!)被称为Metaspace的另一种存储管理机制所替代。显然,Metaspace不包含Java对象。相反,它包含代码段,类描述符和其他JVM内部数据结构。

  7. 在Java的最新版本(即Java 8 u20和更高版本)中,GC具有另一种机制,可对在给定数量的GC周期后仍可幸存的字符串进行重复数据删除。

  8. 字符串的行为(即哪些是被插入的,哪些不是)由JLS和String类的javadocs的相关部分确定。

  9. 如果遵循一个简单的规则,那么所有的复杂性都不相关:

    切勿用于==比较字符串。始终使用equals


现在来处理您的示例代码:

String str1 = "Abc";               // string pool

String str2 = "XYZ";               // string pool

String str3 = str1 + str2;         // not string pool (!!)

String str3a = "Abc" + "XYZ";      // string pool 

String str4 = new String("PQR");   // not string pool (but the "PQR" literal is)

String str5 = str1.concat(str4);   // not string pool 

String str6 = str1 + str4;         // not string pool

String str7 = str6.intern();       // string pool
Run Code Online (Sandbox Code Playgroud)

为什么?

  • 的值分配给str1str2并且str3a是常量表达式的所有值; 见下文。
  • str3根据JLS,分配给它的值不是常量表达式的值。
  • str4-JLS表示new操作员始终创建一个新对象,并且不会自动插入新字符串
  • str5-字符串操作除了intern不在字符串池中创建对象外
  • str6-同上-等同于concat通话。JLS还说+产生一个新的字符串(在常量表达式的情况下除外)。
  • str7-例外:见上文。该intern调用将在字符串池中返回一个对象。

常量表达式包括文字,包含文字的串联,常量的值static final String以及其他一些东西。有关完整列表,请参见JLS 15.28。但请记住,字符串池仅保存字符串值。


的确切行为intern取决于Java版本。考虑以下示例:

char[] chars = // create an array of random characters
String s1 = new String(chars);
String s2 = s1.intern();
Run Code Online (Sandbox Code Playgroud)

让我们假设随机字符不对应于任何以前的中断字符串。

  • 对于在PermGen中分配了内部字符串的较早JVM,intern示例中的调用将(必须)产生一个新String对象。

  • 对于较新的JVM,intern可以将现有的String对象添加到字符串池数据结构中,而无需创建新String对象。

换句话说,真相s1 == s2取决于Java版本。