Перестаньте спрашивать меня, сколько объектов создает новая строка! Я докажу это вам!

Java
Перестаньте спрашивать меня, сколько объектов создает новая строка! Я докажу это вам!

Я думаю, что все программисты Java были обеспокоены этим новым вопросом String.Это очень частый вопрос Java-интервью, но, к сожалению, в Интернете есть разные мнения, и стандартного ответа нет. Некоторые люди говорят, что создается 1 объект, некоторые люди говорят, что создаются 2 объекта, а некоторые люди говорят, что могут быть созданы 1 или 2 объекта, но ни у кого нет никаких доказательств, чтобы убить другую сторону, что делает нас едоками дыни. оказались перед дилеммой, не зная, кому можно доверять.

Но сегодня Лао Ван осмелился поговорить на эту тему со всеми, и я, кстати, вынесу еще несколькосвидетельство.

Исходя из текущей ситуации, оnew String("xxx")Есть 3 ответа на количество созданных объектов:

  1. Некоторые говорят, что создается 1 объект;
  2. Некоторые говорят, что создаются 2 объекта;
  3. Некоторые говорят, что создаются 1 или 2 объекта.

Ключевым моментом разногласий, где есть несколько ответов, является «пул строковых констант»., некоторые говорят, что новый строковый метод создаст строковый объект в пуле констант, а некоторые говорят, что при использовании новой строки он не будет создавать объект в пуле строковых констант, а будет вызыватьintern()метод, он перейдет к пулу строковых констант для обнаружения и создания строки.

Итак, давайте сначала поговорим об этом «пуле строковых констант».

Пул строковых констант

Выделение строк, как и размещение других объектов, требует больших затрат времени и места. Если необходимо часто создавать большое количество строк, это сильно повлияет на производительность программы. Поэтому для повышения производительности и уменьшения накладных расходов памяти , JVM представляет концепцию пула строковых констант (таблица пула констант).

Пул строковых констант эквивалентен открытию пространства пула констант для строки, похожего на область буфера. Для напрямую назначенной строки (строка s="xxx") существующая строковая константа предпочтительно используется каждый раз при создании строки. Строка пула, если в пуле строковых констант нет связанной строки, строка будет сначала создана в пуле строковых констант, а затем адрес ссылки будет возвращен в переменную, как показано на следующем рисунке:

字符串常量池示意图.png
Приведенное выше утверждение может быть подтверждено следующим кодом:

public class StringExample {
    public static void main(String[] args) {
        String s1 = "Java";
        String s2 = "Java";
        System.out.println(s1 == s2);
    }
}

Результат выполнения вышеуказанной программы:true, указывая, что переменная s1 и переменная s2 указывают на один и тот же адрес.

Здесь мы поговорим об изменениях пула строковых констант в разных версиях JDK.

Структура памяти постоянного пула

отПосле JDK 1.7 метапространство, замененное бессмертным поколением, переместило пул строковых констант из области методов в кучу Java..

Схема памяти JDK 1.7 показана ниже:

JDK 1.7 内存布局.png
Схема памяти JDK 1.8 показана на следующем рисунке:
JDK 1.8 内存布局.png
Самая большая разница между JDK 1.8 и JDK 1.7 заключается в том, что JDK 1.8 отменяет постоянное создание и устанавливает метапространство.. Официальное заявлениеПоскольку памяти постоянного поколения часто бывает недостаточно или возникают утечки памяти, появится исключение java.lang.OutOfMemoryError: PermGen, поэтому постоянная область заброшена и вместо нее используется метапространство, а вместо этого используется локальная память.Официальный сайт объясняет детали:openjdk.java.net/jeps/122

расшифровка ответа

Люди, которые думают, что новый способ создает 1 объект, думают, что новая строка просто создает объект в куче, только при использованииintern()Только после этого переходим к пулу констант, чтобы найти и создать строку.

Люди, которые думают, что новый способ создает 2 объекта, думают, что новая строка создает объект в куче, а также создает строку в пуле строковых констант.

Люди, которые думают, что в новом методе можно создать 1 или 2 объекта, думают, что new String сначала пойдет в пул констант, чтобы определить, есть ли такая строка, и если да, то только создаст строку в куче и указать на строку в пуле констант, если строка не существует в пуле констант, будут созданы два объекта, сначала создайте строку в пуле констант, а затем верните ссылку на объект в куче, как показано на следующий рисунок:

new 字符串常量池.png

Фараон думает, что правильный ответ: создать 1 или 2 предмета.

техническая демонстрация

Чтобы решить звонок, звонок должен быть привязан.Возвращаясь к спорному моменту вопроса, будет ли новая строка создавать символы в постоянном пуле? Правильный вывод мы можем сделать, декомпилировав следующий код, код выглядит следующим образом:

public class StringExample {
    public static void main(String[] args) {
        String s1 = new String("javaer-wang");
        String s2 = "wang-javaer";
        String s3 = "wang-javaer";
    }
}

Сначала мы используемjavac StringExample.javaСкомпилируем код, а затем используемjavap -v StringExampleПросмотрите скомпилированные результаты, соответствующая информация выглядит следующим образом:

Classfile /Users/admin/github/blog-example/blog-example/src/main/java/com/example/StringExample.class
  Last modified 2020年4月16日; size 401 bytes
  SHA-256 checksum 89833a7365ef2930ac1bc3d7b88dcc5162da4b98996eaac397940d8997c94d8e
  Compiled from "StringExample.java"
public class com.example.StringExample
  minor version: 0
  major version: 58
  flags: (0x0021) ACC_PUBLIC, ACC_SUPER
  this_class: #16                         // com/example/StringExample
  super_class: #2                         // java/lang/Object
  interfaces: 0, fields: 0, methods: 2, attributes: 1
Constant pool:
   #1 = Methodref          #2.#3          // java/lang/Object."<init>":()V
   #2 = Class              #4             // java/lang/Object
   #3 = NameAndType        #5:#6          // "<init>":()V
   #4 = Utf8               java/lang/Object
   #5 = Utf8               <init>
   #6 = Utf8               ()V
   #7 = Class              #8             // java/lang/String
   #8 = Utf8               java/lang/String
   #9 = String             #10            // javaer-wang
  #10 = Utf8               javaer-wang
  #11 = Methodref          #7.#12         // java/lang/String."<init>":(Ljava/lang/String;)V
  #12 = NameAndType        #5:#13         // "<init>":(Ljava/lang/String;)V
  #13 = Utf8               (Ljava/lang/String;)V
  #14 = String             #15            // wang-javaer
  #15 = Utf8               wang-javaer
  #16 = Class              #17            // com/example/StringExample
  #17 = Utf8               com/example/StringExample
  #18 = Utf8               Code
  #19 = Utf8               LineNumberTable
  #20 = Utf8               main
  #21 = Utf8               ([Ljava/lang/String;)V
  #22 = Utf8               SourceFile
  #23 = Utf8               StringExample.java
{
  public com.example.StringExample();
    descriptor: ()V
    flags: (0x0001) ACC_PUBLIC
    Code:
      stack=1, locals=1, args_size=1
         0: aload_0
         1: invokespecial #1                  // Method java/lang/Object."<init>":()V
         4: return
      LineNumberTable:
        line 3: 0

  public static void main(java.lang.String[]);
    descriptor: ([Ljava/lang/String;)V
    flags: (0x0009) ACC_PUBLIC, ACC_STATIC
    Code:
      stack=3, locals=4, args_size=1
         0: new           #7                  // class java/lang/String
         3: dup
         4: ldc           #9                  // String javaer-wang
         6: invokespecial #11                 // Method java/lang/String."<init>":(Ljava/lang/String;)V
         9: astore_1
        10: ldc           #14                 // String wang-javaer
        12: astore_2
        13: ldc           #14                 // String wang-javaer
        15: astore_3
        16: return
      LineNumberTable:
        line 5: 0
        line 6: 10
        line 7: 13
        line 8: 16
}
SourceFile: "StringExample.java"

Примечание. Средой выполнения приведенного выше кода также является jdk1.8.0_101.

вConstant poolПредставляет пул строковых констант, мы нашли нас в пуле строковых констант во время компиляции строки.String s1 = new String("javaer-wang");Символ "javaer-wang", определенный в сообщении#10 = Utf8 javaer-wangВидно, чтоСтроки, созданные new во время компиляции, будут помещены в пул строковых констант во время компиляции., то есть новая строка Метод сначала оценивает пул строковых констант. Если новой строки нет, то будут созданы 2 объекта. Если он уже существует, в куче будет создан только один объект, указывающий на строку в пуле строковых констант.

Итак, вопрос в том, является ли результат выполнения следующего кода истинным или ложным?

String s1 = new String("javaer-wang");
String s2 = new String("javaer-wang");
System.out.println(s1 == s2);

Поскольку new String создает строки в пуле констант, результат выполнения должен быть истинным. На самом деле это не так. Сравниваемые здесь переменные s1 и s2 являются адресами в куче. Поскольку адреса в куче разные, результат должен быть ложным, как показано на следующем рисунке:

字符串引用.png
Из рисунка видно, что ссылки s1 и s2 должны быть одинаковыми, а ссылки s3 и s4 разными, соответствующий программный код выглядит следующим образом:

public static void main(String[] args) {
    String s1 = "Java";
    String s2 = "Java";
    String s3 = new String("Java");
    String s4 = new String("Java");
    System.out.println(s1 == s2);
    System.out.println(s3 == s4);
}

Результат выполнения программы также ожидаем:

true false

расширить знания

Мы знаем, что String является final Modified, что означает, что его нельзя изменить, пока ему не будет присвоено значение. Однако в дополнение к оптимизации пула строковых констант компилятор также оптимизирует строки, которые могут быть подтверждены во время компиляции, например следующий код:

public static void main(String[] args) {
    String s1 = "abc";
    String s2 = "ab" + "c";
    String s3 = "a" + "b" + "c";
    System.out.println(s1 == s2);
    System.out.println(s1 == s3);
}

Согласно идее, что String нельзя изменить, s2 должен создать две строки «ab» и «c» в пуле строковых констант, а s3 создаст три строки, и результат их эталонного сравнения должен быть ложным, но на самом деле нет, все их результаты верны, что является заслугой оптимизации компилятора.

Так же мы используемjavac StringExample.javaСначала скомпилируйте код, а затем используйтеjavap -c StringExampleКоманда для просмотра скомпилированного кода выглядит следующим образом:

警告: 文件 ./StringExample.class 不包含类 StringExample
Compiled from "StringExample.java"
public class com.example.StringExample {
  public com.example.StringExample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: ldc           #7                  // String abc
       2: astore_1
       3: ldc           #7                  // String abc
       5: astore_2
       6: ldc           #7                  // String abc
       8: astore_3
       9: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      12: aload_1
      13: aload_2
      14: if_acmpne     21
      17: iconst_1
      18: goto          22
      21: iconst_0
      22: invokevirtual #15                 // Method java/io/PrintStream.println:(Z)V
      25: getstatic     #9                  // Field java/lang/System.out:Ljava/io/PrintStream;
      28: aload_1
      29: aload_3
      30: if_acmpne     37
      33: iconst_1
      34: goto          38
      37: iconst_0
      38: invokevirtual #15                 // Method java/io/PrintStream.println:(Z)V
      41: return
}

Из кода 3 и 6 видно, что строки оптимизируются компилятором в строку «abc».

Суммировать

В этой статье мы проходимjavap -v XXXПосмотрите на скомпилированный код таким образом, что new String впервые создаст эту строку в пуле строковых констант, то есть создание строки с new может создать 1 или 2 объекта, если пул констант уже существует Эта строка только создаст переменную в куче и укажет на значение в пуле строковых констант.Если в пуле строковых констант нет соответствующего символа, строка будет создана первой, а ссылка на эту строку будет возвращена в кучу пространственная переменная. Мы также представили изменения пула строковых констант в JDK 1.7 и JDK 1.8 и оптимизацию компилятора для определения строки, надеясь помочь вам понять сравнение строк.

последние словаОригинальность непростая.В этой статье почти 3000 текстовых описаний и большое количество красивых картинок, на которые у автора ушло около 5 часов.Писать — это круто и помогает другим, автор надеется продолжить. Если вы найдете это полезным,Пожалуйста, не стесняйтесь нажимать лайк, спасибо.

Для получения более интересного контента, пожалуйста, обратите внимание на публичный аккаунт WeChat «Java Chinese Community».