Пул строковых констант и String#intern()

Java задняя часть JVM программист

String — важный тестовый сайт для основы Java. Есть много вопросов, которые нужно задать, и многие вопросы можно сократить по горизонтали на другие тестовые площадки или по вертикали вглубь JVM.

В этой статье не рассматриваются основы String и основное внимание уделяется String#intern().

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

Строковые константы могут попадать в пул констант два раза:

  1. Время компиляции: объявить в двойных кавычкахконстанты (включаяпоказать заявление,Оптимизация статической компиляцииПоследние константы, такие как «1» + «2», оптимизированы до константы «12»), которая будет статически записана в «пул констант» в файле класса во время внешней компиляции. «Постоянный пул» будет загружен в «постоянный пул в памяти» после загрузки класса, который мы обычно называем постоянным пулом. в то же время,JIT-оптимизацияПодобные константы также могут быть сгенерированы.
  • Время выполнения: вызов метода String#intern(), можно динамически записать объект String в указанный выше «пул констант в памяти».

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

Время 2 ведет себя по-разному в jdk6 и jdk7, обсуждаемых ниже.

String#intern()

Читатели могут напрямую читать ссылки. Следующее резюме предназначено только для удобства обзора обезьян.

утверждение

/** 
 * Returns a canonical representation for the string object. 
 * <p> 
 * A pool of strings, initially empty, is maintained privately by the 
 * class <code>String</code>. 
 * <p> 
 * When the intern method is invoked, if the pool already contains a 
 * string equal to this <code>String</code> object as determined by 
 * the {@link #equals(Object)} method, then the string from the pool is 
 * returned. Otherwise, this <code>String</code> object is added to the 
 * pool and a reference to this <code>String</code> object is returned. 
 * <p> 
 * It follows that for any two strings <code>s</code> and <code>t</code>, 
 * <code>s.intern()&nbsp;==&nbsp;t.intern()</code> is <code>true</code> 
 * if and only if <code>s.equals(t)</code> is <code>true</code>. 
 * <p> 
 * All literal strings and string-valued constant expressions are 
 * interned. String literals are defined in section 3.10.5 of the 
 * <cite>The Java&trade; Language Specification</cite>. 
 * 
 * @return  a string that has the same contents as this string, but is 
 *          guaranteed to be from a pool of unique strings. 
 */  
public native String intern();

String#intern() — это нативный метод. Согласно Javadoc, если текущая строка существует в пуле констант, текущая строка будет возвращена напрямую.Если строка не существует в пуле констант, строка будет помещена в пул констант, а затем возвращена.

Принцип реализации

Наконец, JNI вызывает метод StringTable::intern(), реализованный в C++:

oop StringTable::intern(Handle string_or_null, jchar* name,  
                        int len, TRAPS) {  
  unsigned int hashValue = java_lang_String::hash_string(name, len);  
  int index = the_table()->hash_to_index(hashValue);  
  oop string = the_table()->lookup(index, name, len, hashValue);  
  // Found  
  if (string != NULL) return string;  
  // Otherwise, add to symbol to table  
  return the_table()->basic_add(index, string_or_null, name, len,  
                                hashValue, CHECK_NULL);  
}
oop StringTable::lookup(int index, jchar* name,  
                        int len, unsigned int hash) {  
  for (HashtableEntry<oop>* l = bucket(index); l != NULL; l = l->next()) {  
    if (l->hash() == hash) {  
      if (java_lang_String::equals(l->literal(), name, len)) {  
        return l->literal();  
      }  
    }  
  }  
  return NULL;  
}

Найдите строку в хеш-таблице, возвращаемую функцией the_table(), верните ее, если она существует, иначе присоединитесь к таблице.

StringTable — этоисправленный размеризHashtable, размер по умолчанию — 1009. Основная логика такая же, как у HashMap в Java, и для решения проблемы коллизий также используется метод zipper.

Поскольку это метод застежки-молнии, если вставить много строк, коллизия будет усугубляться, что приведет к очень длинному связанному списку. В худшем случае производительность String#intern() снижается с O(1) до O(n).

  • Длина StringTable в jdk6 фиксирована и равна 1009.
  • В jdk7 длину StringTable можно передать как параметр-XX:StringTableSizeУкажите, по умолчанию 1009.

Разница между String#intern() в jdk6 и jdk7

введение

Я полагаю, что многие Java-программисты делают то же самое.String s = new String("abc");Этот оператор создает заголовок нескольких объектов. В этом разделе в основном исследуется мастерство программиста в работе с константным пулом строковых объектов. Приведенный выше оператор создает 2 объекта:

  • Первый объект с содержимым «abc» хранится в пуле констант.
  • Второй объект, содержимое «abc», хранится в куче.

проблема

Взгляните на кусок кода:

public static void main(String[] args) {
    String s = new String("1");
    s.intern();
    String s2 = "1";
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    s3.intern();
    String s4 = "11";
    System.out.println(s3 == s4);
}

распечатать результат:

# jdk6下
false false
# jdk7下
false true

Конкретная причина будет объяснена позже, а затемs3.intern();Заявление опускается на одну строку и помещается вString s4 = "11";позже. будетs.intern();ставитьString s2 = "1";позже:

public static void main(String[] args) {
    String s = new String("1");
    String s2 = "1";
    s.intern();
    System.out.println(s == s2);

    String s3 = new String("1") + new String("1");
    String s4 = "11";
    s3.intern();
    System.out.println(s3 == s4);
}

распечатать результат:

# jdk6下
false false
# jdk7下
false false

Объяснение jdk6

image.png

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

В jdk6 все приведенные выше отпечатки ложны.

Потому что постоянный пул jdk6 размещен в Пермской области, которая полностью отделена от обычной Кучи (имеются в виду области Эдем, Выживший и Старый). А именно: Строки, объявленные в кавычках, напрямую загружаются в пул констант посредством компиляции и загрузки классов и располагаются в области Perm; новые объекты String располагаются в Heap (E, S, O). Сравнение адреса объекта Пермского края с адресом объекта в Куче — это точно не одно и то же.

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

Объяснение jdk7

В jdk6 и более ранних версиях пул строковых констант размещается в Пермской области. По умолчанию размер Пермской области всего 4М.Если поставить более крупные стринги, то легко скинутьOutOfMemoryError: PermGen space.

Поэтому jdk7 переместил пул строковых констант из пермской области в обычную кучу (E, S, O).

Пермский край – это постоянное поколение. Легко столкнуться с переполнением памяти, когда область методов реализована самой постоянной генерацией, а содержимое, хранящееся в области методов, также трудно оценить по размеру, и нет необходимости управлять им в куче. jdk8 отменил постоянную генерацию и создал новую область метода реализации Metaspace вне кучи.

Именно потому, что пул строковых констант был перемещен в кучу, произошли вышеуказанные изменения.

первый кусок кода

image.png

Сначала посмотрите на s3 и s4:

  • Во-первых,String s3 = new String("1") + new String("1");, создание нескольких объектов,s3 в конечном итоге указывает на «11» в куче. Обратите внимание, что в настоящее время в пуле констант нет строки «11».
  • Потом,s3.intern();, поместите строку «11» в s3 в пул констант, поскольку в настоящее время строка «11» не существует в пуле констант, поэтому общая практика такая же, как у jdk6, и создается объект String «11». в пуле констант ——Однако пул констант в jdk7 уже не в пермской области, и внесены соответствующие коррективы: не нужно хранить объект в пуле констант, а напрямую хранить ссылку в куче, то есть ссылочный адрес s3.
  • Следующий,String s4 = "11";, "11" объявляется в двойных кавычках, поэтому для его поиска он будет напрямую обращаться к пулу констант, если он не будет создан снова. Обнаружено, что эта строка уже существует, которая только что была переданаs3.intern();Ссылочный адрес s3 хранится в пуле констант. Итак, напрямую верните ссылочный адрес s3,s4 назначается ссылкой на s3, а s4 указывает на «11» в куче.
  • Наконец, «11» в куче, на которую указывают s3 и s4, ссылка на s3 хранится в пуле констант, что удовлетворяетs3 == s4.

Посмотрите на s и s2 еще раз:

  • Во-первых,String s = new String("1");, генерируются 2 объекта, "1" в пуле констант и "1" в куче,s указывает на "1" в куче.
  • Потом,s.intern();, предыдущее предложение уже создало "1" в пуле констант, так что здесь ничего не делайте.
  • Следующий,String s2 = "1";, в постоянном пуле есть "1", поэтомуs2 указывает непосредственно на «1» в пуле констант.
  • Наконец, «1» в куче, на которую указывает s, «1» в пуле констант, на который указывает s2, и строка «1», хранящаяся в пуле констант, не удовлетворяется.s == s2.

второй кусок кода

image.png

Сначала посмотрите на s3 и s4,s3.intern();ставитьString s4 = "11";Задний:

  • выполнить первымString s4 = "11";, в настоящее время «11» не существует в пуле констант, поэтому поместите «11» в пул констант, а затемs4 указывает на «11» в постоянном пуле.
  • повторно выполнитьs3.intern();, предыдущее предложение уже создало "11" в пуле констант, так что здесь ничего не делайте.
  • Наконец, s3 по-прежнему указывает на «11» в куче, s4 указывает на «11» в пуле констант, а строка «11» хранится в пуле констант, что больше не выполняется.s3 == s4.

Глядя на s и s2 снова,s.intern();ставитьString s2 = "1";Задний:

  • выполнить первымString s2 = "1";, ранее пройденныйString s = new String("1");"1" создается в постоянном пуле, поэтомуs2 указывает непосредственно на «1» в пуле констант.
  • повторно выполнитьs.intern();, в константном пуле есть "1", поэтому здесь ничего не делается.
  • Наконец, «1» в куче, на которую указывает s, «1» в пуле констант, на который указывает s2, и строка «1», хранящаяся в пуле констант, по-прежнему не выполняется.s == s2.

Сводка различий

По сравнению с jdk6, jdk7 изменил расположение пула констант String и семантику String#intern():

  • Перемещен пул констант String из области Perm в область Heap.
  • При вызове метода String#intern(), если строка существует в куче, но не в пуле констант, ссылка на объект в куче напрямую сохраняется в пуле констант без повторного создания объекта в пуле констант.

использовать позу

Рекомендуется прямое чтение ссылок.

дополнительный вопрос

Основное использование String#intern() выглядит следующим образом:

String s1 = xxx1.toString().intern();
String s2 = xxx2.toString().intern();
assert s1 == s2;

Тем не мение,xxx1.toString(),xxx2.toString()Были созданы два анонимных объекта String, после чего вызывается String#intern(). Так,Куда делись эти два анонимных объекта??

Подсчитано, что у обезьяны проблемы с пониманием процесса создания объектов, возможноxxx1.toString()Возврат без сохранения объекта в кучу? Может быть, какой-то синтаксический сахар в String#intern()?

Позже будет время ее решить. . .


Ссылаться на:


Ссылка на эту статью:

Ссылка на эту статью:Пул строковых констант и String#intern()
автор:обезьяна 007
Источник:monkeysayhi.github.io
Эта статья основана наCreative Commons Attribution-ShareAlike 4.0Международное лицензионное соглашение выпущено, его можно перепечатать, вывести или использовать в коммерческих целях, но авторство и ссылка на эту статью должны быть сохранены.