String s = new String("xyz") создает несколько экземпляров, вы действительно понимаете это правильно?

Java
String s = new String("xyz") создает несколько экземпляров, вы действительно понимаете это правильно?

Начните с вопросов для интервью

String s = new String("xyz"); 创建了几个实例?

Это очень классический вопрос для интервью. В так называемой книге по Java «стандартный ответ», который я видел, выглядит так:

两个,一个堆区的“xyz”,一个栈区指向“xyz”的s。

В этом так называемом «стандартном ответе» слишком много слотов, и мы проанализируем его позже.

Хотя ответ возмутителен, я не думаю, что сам вопрос имеет смысл, потому что вопрос не определяет ни конкретное значение «созданного», ни время «созданного», это время выполнения? Когда пакет не включает загрузку классов? Есть ли контекст кода context? Также не определено, к чему относится экземпляр, является ли он экземпляром Java? Или это единственная ссылка на экземпляр String? Пакет не включает экземпляр C++ в JVM?

Судя по всему, этот вопрос является «проблемной проблемой». Этот ответ также является «сомнительным ответом».

Строковая структура

Перед анализом, чтобы потом облегчить отрисовку карты памяти, нам необходимо иметь общее представление о структуре String в Java:

image-20201224220819475

Как видно из рисунка выше, класс String имеет три свойства:

  • значение: массив символов, используемый для хранения символов.

  • hash: хэш-код кэшированной строки, по умолчанию 0 (на самом деле хэш-значение строки называетсяhashCodeметод будет рассчитан).

  • serialVersionUID: используется для сериализации.

Нормальные вопросы и разумные объяснения

Добавьте несколько квалификаторов в основу выше, чтобы получить новый вопрос:

String s = new String("xyz");创建几个String实例?

На этот вопрос вы можете найти несколько высоко оцененных ответов в Интернете:

两个。
一个是字符串字面量"xyz"所对应的、存在于全局共享的常量池中的实例,
另一个是通过new String(String)创建并初始化的、内容(字符)与"xyz"相同的实例。
考虑到如果常量池中如果有这个字符串,就只会创建一个。
同时在栈区还会有一个对new出来的String实例的s。

Принимая во внимание стек и кучу, а также упоминание постоянного пула, я думаю, что это оправдало ожидания большинства интервьюеров в отношении ответа на этот вопрос, и, возможно, это также то, что интервьюер хочет изучить.

Но этот ответ является лишь разумным и не совсем правильным.

Прежде всего, я не понимаю, почему многие респонденты всегда используют «постоянный пул» вместо «постоянный пул строк». В системе Java на самом деле существует три пула констант, а также концепции и использование трех пулов констант. бесполезны.Одно и то же, смешивая их вместе, можно легко вызвать непонимание у других.

Во-вторых, даже если «пул констант», упомянутый ответчиком, является «пулом констант строк», «пул констант строк» ​​хранит ссылки на экземпляры String вместо строк, что очень отличается. И этот ответ не учитывает среду, в которой выполняется код.

Эти вопросы будут проанализированы один за другим ниже.

Различать переменные и экземпляры

Вернемся к вопросу и "стандартному ответу" в начале:

问题:String s = new String("xyz"); 创建了几个实例?
答案:两个,一个堆区的“xyz”,一个栈区指向“xyz”的s

Очевидно, что человек, написавший ответ, не различал переменные и экземпляры. В Java переменная — это переменная, а переменная типа предназначена только для экземпляра объекта или нулевого значения, а не для самого экземпляра. Количество объявленных переменных не обязательно связано с количеством созданных экземпляров.

Например:

String s1 = "xyz";  
String s2 = s1.concat("");  
String s3 = null;  
new String(s1);  

Этот код будет включать 3 переменные типа String:

  • s1, указывающий на 1 экземпляр String ниже
  • s2, указывающий на то же, что и s1
  • s3, значение равно null, не указывает ни на один экземпляр

и 3 экземпляра строки:

  • Строковый экземпляр резидентной строковой константы, соответствующей литералу "xyz"
  • Экземпляр String резидентной строковой константы, соответствующий литералу ""
  • Новый экземпляр String, созданный с помощью новой строки (String) без какой-либо переменной, указывающей на него.

загрузка класса

Для String s = new String("xyz"); создать несколько экземпляров String? Эта проблема.

Кажется, что все ответы в сети анализируют процесс загрузки класса и фактический процесс выполнения вместе.

Вроде бы и нет проблемы, ведь для выполнения фрагмента кода должен быть загружен класс, в котором он находится, а для одного и того же загрузчика классов он загружается не более одного раза.

Но давайте посмотрим на байт-код этого кода:

image-20201226155615626

Кажется, появляется только один разnew java/lang/String, то есть создается только один экземпляр String. То есть код в исходном вопросе будет создавать только новый экземпляр String при каждом его выполнении. Здесь инструкция ldc просто помещает ссылку на экземпляр String ("xyz"), ранее созданный в процессе загрузки класса, на вершину стека операндов и не создает новый экземпляр String.

Разве не должно быть двух экземпляров? И когда создается экземпляр String?

Все мы знаем, что фаза синтаксического анализа загрузки класса — это процесс, в котором виртуальная машина Java заменяет символические ссылки в пуле констант прямыми ссылками.Согласно спецификации JVM, реализация JVM, соответствующая спецификации, должна создавать и размещать Строковый экземпляр как константа в процессе загрузки класса, чтобы соответствовать литералу "xyz", который выполняется на этапе синтаксического анализа загрузки класса. Эта константа используется глобально, и новый экземпляр String необходимо создавать только в том случае, если предыдущая строка с таким же содержимым уже не существует.

Таким образом, вы можете понять, что на этапе синтаксического анализа загрузки класса фактически был создан экземпляр String, а при выполнении кода создается новый экземпляр String. Конечно, нет ничего плохого в том, чтобы обсуждать их вместе.

JVM-оптимизация

Приведенное выше обсуждение относится только к языку Java и виртуальной машине Java, определенным в спецификации. Концептуально это верно, но фактическая реализация JVM может быть более оптимизирована, и фрагмент кода в исходном вопросе может быть создан не полностью (без выделенного места) при фактическом выполнении фрагмента кода.

Говорить, что это «стандартный ответ» без контекстуального кода — хулиганство.

Давайте посмотрим на этот код:

image-20201226130324887

Выполнение этого кода будет постоянно создавать объекты String, потребляющие память, а затем часто вызывать GC.

Полагаю, что у всех нет мнения на этот вывод, добавим-XX:+PrintGC -XX:-DoEscapeAnalysisРаспечатайте лог и отключите escape-анализ (JDK8 включает эту оптимизацию по умолчанию, давайте сначала отключим ее)

image-20201226130324887

Запустите его, чтобы увидеть:

image-20201226130324887

Результат действительно такой, как мы и ожидали, постоянное создание объектов String съедает память и приводит к частому сбору мусора.

мы сейчас-XX:-DoEscapeAnalysisизменить на-XX:+DoEscapeAnalysis, повторно запустите этот код:

image-20201226130324887

Произошла волшебная вещь, и журнал GC больше не печатался, если я продолжал работать. Не потребляет ли вновь созданный объект String память?

Фактическая ситуация такова: после оптимизации HotSpot VM метод newString() не будет создавать новый экземпляр String. Это, естественно, не ест память и больше не запускает GC.

Теперь давайте посмотрим на вопрос в начале: не рассматривая конкретную ситуацию, можем ли мы просто сказать, что String s = new String("xyz"); создаст два экземпляра String?

Я только что привел пример escape-анализа, HotSpot VM также имеет множество подобных оптимизаций, таких как встраивание методов, скалярная подстановка и удаление мертвого кода.

klass-oop

Если атрибуция экземпляра «Java» не добавлена ​​в основу вопроса, то мы не должны игнорировать экземпляр oop в JVM.

Чтобы лучше объяснить это позже, необходимо дополнить знание модели klass-opp. Сначала договоритесь, поскольку содержание полного текста касается конкретной реализации JVM, оно основано на виртуальной машине HotSpot в Jdk8.

HotSpot VM реализован на основе C++, а C++ является объектно-ориентированным языком, который сам по себе имеет основные характеристики объектно-ориентированного, поэтому для представления объектов в Java проще всего сгенерировать класс C++ для каждого класса Java, соответствующего это . Но HotSpot VM этого не сделала, а спроектировала модель класс-уп.

klass, которая является формой, в которой метаинформация классов Java существует в JVM. После загрузки класса Java загрузчиком классов JVM он существует в JVM в виде класса.

image-20201225180727729

oop, которая представляет собой форму, в которой объекты Java существуют в JVM. Каждый раз, когда создается новый объект, объект ООП соответствующего типа создается соответственно внутри JVM.

где instanceOopDesc ​​представляет объект, не являющийся массивом, а arrayOopDesc ​​представляет объект массива;

И objArrayOopDesc ​​представляет объект массива ссылочного типа, typeArrayOopDesc ​​представляет объект массива базового типа.

Например: экземпляр класса String в Java будет иметь соответствующий экземпляр instanceOopDesc ​​в JVM.

image-20201225180727729

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

В системе Java существует три вида постоянных пулов:

  • Постоянный пул в байт-коде класса: существует на диске. В основном он хранит два типа констант: литералы и символические ссылки.

  • Постоянный пул времени выполнения: часть области метода. Пул констант, о котором мы часто говорим, относится к этой области: пул констант времени выполнения в области методов.

  • Пул строковых констант: существует в области кучи. Этот постоянный пул представляет собой StringTable на уровне JVM, в котором хранятся только ссылки на экземпляры java.lang.String, а не содержимое объектов String. Вообще говоря, когда мы говорим, что строка входит в пул строковых констант, это означает, что в этой StringTable сохранена ссылка на нее, и наоборот, если ее нет в ней, значит, в StringTable на нее нет ссылки. .

Сегодня мы узнаем о пуле строковых констант.

Пул строковых констант, а именно String Pool. Соответствующим классом в JVM является StringTable, а базовой реализацией является Hashtable. Используется идея хеширования.

image-20201225180504262

Следующий код предназначен для добавления строкового метода в пул строковых констант. Хотя это код C++, я считаю, что любой, кто изучал Java, может понять его или, по крайней мере, понять, что делает этот код. Индекс нижнего индекса находится по хеш-значению, сгенерированному содержимым String + length, а затем instanceOopDesc, соответствующий экземпляру класса String Java, инкапсулируется в HashtableEntry в качестве структуры хранения и сохраняется в пуле констант.

image-20201225190419815

Дополнив знания о пуле строковых констант, вернемся к вопросу в начале статьи:

String s = new String("xyz"); Сколько экземпляров создано?

Мы рисуем карту памяти, и два экземпляра instanceOopDesc, соответствующие String, на рисунке опущены.

image-20201226115834730

Нетрудно придумать ответ:

如果包括JVM中的C++实例的话,
有两个Java的String实例,
两个String实例对应的instanceOopDesc实例,
还有一个char[]数组对应的typeArrayOopDesc实例。
加一起一共是5个,也可以说2个String实例加上3个oop实例。

Суммировать

String s = new String("xyz"); Сколько экземпляров создано?

Благодаря приведенному выше анализу мы обнаружим, что каждый раз, когда атрибут добавляется к основе вопроса этого вопроса, этот вопрос будет иметь другой ответ.

Рассматривать ли процесс загрузки класса, рассматривать ли оптимизацию JVM, включать ли соответствующий oop-экземпляр и т. д., стоит поговорить о каждом пункте.

В следующий раз, когда кто-то спросит вас, вы можете поделиться с ним этой статьей.

напиши в конце

Чтобы написать эту статью, я прочитал много блогов старшего @RednaxelaFX и старшего Чжоу Чжимина и многому научился в процессе. Хочется поблагодарить предшественников за вклад в популяризацию и развитие отечественной JVM!

Там тоже очень интересная история.Когда я искал информацию на тему "Как понять String через HSDB", то увидел хорошо написанную статью, восклицающую, что в Китае так много сдержанных богов, и добавил статью Позже в публичном аккаунте рядом с ним я обнаружил, что этот великий бог оказался оскорбленным предшественником основателя PerfMa «Hanquanzi» Ли Цзяпэна!

последний из последних

У меня мало таланта и знаний, а в статье неизбежно будут огрехи, если найдёте, то смело присылайте, я исправлю.

Спасибо, что читаете, ваши лайки и комментарии - это ободрение и поддержка для меня.

Если у вас есть что-то, что вы хотите сообщить мне, вы можете подписаться на мой общедоступный аккаунт WeChat «CoderW», очень добро пожаловать и спасибо за внимание!


Код, задействованный в тексте:GitHub.com/Сяо Инчжи…

Спецификация JVM Java SE 8 Edition:docs.Oracle.com/JavaColor/spec…

Справочная статья:возможно. только /posts/view/…

Справочная статья:Woohoo. ITeye.com/blog/ возиться с этими…

Справочная статья:обожаю St блог Talent/blog/2014/0…