Подробный рассказ о реализации String.intern() в JVM

Java задняя часть JVM Unicode
Подробный рассказ о реализации String.intern() в JVM

предисловие

струнныйinternМетод может быть менее используемым и незнакомым для всех, хотя его не рекомендуется использовать в реальных проектах.internметод, аналогичный пул может быть реализован на уровне Java, но нам все равно нужно знать, что его принципиальный механизм — нет.

О стажерском методе

Через этот метод может быть возвращен стандартный объект String. В JVM имеет специальный пул String Constance для поддержания этих стандартных объектов. Постоянный пул является структурой HASH MAP, а также строковые объектные вызовыinternМетод сначала проверит, существует ли уже стандартный объект строки в пуле.Если он существует, он напрямую вернет стандартный объект.Если он не существует, он создаст стандартный объект в пуле и вернет объект.

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

каков эффект

ТакinternЧто делает метод? Ранее мы знали, что уровень Java проходит до тех пор, пока значения строк равны.internПриобретенный должен быть тот же объект, то есть так называемые стандартные объекты. Такие как следующие

String st = new String("hello world");
String st2 = new String("hello world");
System.out.println(st.intern() == st2.intern());

Нашли? мы можем использовать==Давайте сравним значения двух объектов, Вы должны знать, что в Java это сравнение может только определить, являются ли они одной и той же ссылкой, но поinternПосле того, как метод обработан, его можно напрямую сравнить таким образом, по сравнению сequalsНо это намного быстрее, и производительность повышается. Вы можете сказать да, потому чтоinternсделали что-то вродеequalsОперация сравнения сделана, здесь она еще будет очень трудоемкой! Да, вы правы, но если я буду делать несколько сравнений позже, разве это не преимущество, просто сделайте это один раз?equalsВсе сравнения можно использовать позже==Сделайте быстрое сравнение.

Кроме того, эффект экономии памяти также может быть достигнут в некоторых сценариях, таких как сохранение большого количества строковых объектов, которые могут повторяться, например, 100 000 строковых объектов и 90 000 строковых объектов с одним и тем же строковым значением, а затем передачаinternЭтот метод может уменьшить количество строковых объектов до 10 000, а объекты с одинаковым значением используют один и тот же стандартный объект.

Присоединиться к пулу констант времени выполнения

Есть два способа добавить строковые объекты в пул констант времени выполнения на уровне Java:

  • Используйте двойные кавычки непосредственно в программе, чтобы объявить строковый объект, и объект будет добавлен в пул констант во время выполнения. Например, после того, как класс скомпилирован в байт-код, во время выполнения есть соответствующие инструкции по добавлению его в пул констант.
public class Test{
    public static void main(String[] args){
        String s = "hello";
    }
}
  • Другой - через класс Stringinternметод, он может определить, существует ли уже текущая строка в пуле констант, и если нет, то добавить ее в пул констант. Например ниже,
String s = new String("hello");
s.intern();

другой пример

JDK9.

public class Test {
	public static void main(String[] args) {
		String s = new String("hello");
		String ss = new String("hello");
		System.out.println(ss == s);
		String sss = s.intern();
		System.out.println(sss == s);
		String ssss = ss.intern();
		System.out.println(ssss == sss);

		System.out.println("=========");

		String s2 = "hello2";
		String ss2 = new String("hello2");
		System.out.println(ss2 == s2);
		String sss2 = s2.intern();
		System.out.println(sss2 == s2);
		String ssss2 = ss2.intern();
		System.out.println(ssss2 == sss2);
	}
}
false
false
true
=========
false
true
true

Реализация постоянного пула

Слой Java очень прост, простоinternОпределен как собственный метод.

public native String intern();

соответствуетJVM_InternStringфункционируют, главным образом, за счетJNIHandles::resolve_non_nullФункция преобразуется в указатель oop уровня JVM, а затем вызываетсяStringTable::internФункция получает окончательный возвращенный объект и, наконец, передаетJNIHandles::make_localПреобразование в объект уровня Java и возврат.

JNIEXPORT jobject JNICALL
Java_java_lang_String_intern(JNIEnv *env, jobject this)
{
    return JVM_InternString(env, this);
}

JVM_ENTRY(jstring, JVM_InternString(JNIEnv *env, jstring str))
  JVMWrapper("JVM_InternString");
  JvmtiVMObjectAllocEventCollector oam;
  if (str == NULL) return NULL;
  oop string = JNIHandles::resolve_non_null(str);
  oop result = StringTable::intern(string, CHECK_NULL);
  return (jstring) JNIHandles::make_local(env, result);
JVM_END

В основном см.StringTable::intern, StringTable — это пул констант, используемый средой выполнения JVM для хранения констант. Его структура представляет собой хеш-карту, примерно как показано на следующем рисунке.

Основная логика состоит в том, чтобы сначала вычислить длину кодировки юникода, соответствующей строке в кодировке utf-8, создать новый массив в соответствии с длиной, требуемой кодировкой юникода, преобразовать строку в кодировку юникода и, наконец, вызвать другойinternфункция.

oop StringTable::intern(const char* utf8_string, TRAPS) {
  if (utf8_string == NULL) return NULL;
  ResourceMark rm(THREAD);
  int length = UTF8::unicode_length(utf8_string);
  jchar* chars = NEW_RESOURCE_ARRAY(jchar, length);
  UTF8::convert_to_unicode(utf8_string, chars, length);
  Handle string;
  oop result = intern(string, chars, length, CHECK_NULL);
  return result;
}

Логика следующая,

  1. пройти черезjava_lang_String::hash_codeПолучите хэш-значение.
  2. Вызывается по хеш-значениюlookup_sharedФункция смотрит, имеет ли общая хеш-таблица значение строкового объекта, если есть прямой возврат к найденному объекту, функция будет вызываться косвенноlookupфункция, которая будет дополнительно проанализирована позже.
  3. Используется ли другой алгоритм хеширования, и если да, то пересчитать хэш-значение.
  4. пройти черезhash_to_indexФункция вычисляет значение индекса, соответствующее хеш-значению.
  5. пройти черезlookup_in_main_tableФункция ищет строковый объект в корзине, соответствующей значению индекса, и возвращает объект, если он найден.
  6. Если в хэш-таблице ничего из вышеперечисленного не найдено, необходимо добавить его в таблицу, использоватьMutexLockerзакрой, потом позвониbasic_addФункция завершает операцию сложения, и функция будет дополнительно проанализирована позже.
  7. Возвращает строковый объект.
oop StringTable::intern(Handle string_or_null, jchar* name,
                        int len, TRAPS) {
  unsigned int hashValue = java_lang_String::hash_code(name, len);
  oop found_string = lookup_shared(name, len, hashValue);
  if (found_string != NULL) {
    return found_string;
  }
  if (use_alternate_hashcode()) {
    hashValue = alt_hash_string(name, len);
  }
  int index = the_table()->hash_to_index(hashValue);
  found_string = the_table()->lookup_in_main_table(index, name, len, hashValue);

  if (found_string != NULL) {
    if (found_string != string_or_null()) {
      ensure_string_alive(found_string);
    }
    return found_string;
  }
  Handle string;
  if (!string_or_null.is_null()) {
    string = string_or_null;
  } else {
    string = java_lang_String::create_from_unicode(name, len, CHECK_NULL);
  }
  oop added_or_found;
  {
    MutexLocker ml(StringTable_lock, THREAD);
    added_or_found = the_table()->basic_add(index, string, name, len,
                                  hashValue, CHECK_NULL);
  }

  if (added_or_found != string()) {
    ensure_string_alive(added_or_found);
  }

  return added_or_found;
}

Постоянный пул — это хэш-таблица, так каково же количество сегментов по умолчанию? Глядя на приведенные ниже определения, по умолчанию он равен 60013 в 64-разрядных системах и 1009 в 32-разрядных системах.

const int defaultStringTableSize = NOT_LP64(1009) LP64_ONLY(60013);

Логика поиска хеш-таблицы такова:

  1. Хеш-значение вычисляется путем взятия остатка от количества сегментов для получения индекса.
  2. Получите соответствующую информацию о сегменте с помощью индекса.
  3. Получите смещение ковша.
  4. Получите тип ведра.
  5. Получить вход.
  6. еслиVALUE_ONLY_BUCKET_TYPEтипа ведро, объект, соответствующий смещению, декодируется напрямую.Каждая запись в этом типе записей имеет только один 4 байта для представления смещения, то естьu4 offset;.
  7. Если это обычный тип корзины, просмотрите запись, чтобы найти смещение, соответствующее записи с указанным хеш-значением, а затем декодируйте объект, соответствующий смещению. Каждая запись в записях имеет 8 байт, а структураu4 hash;union {u4 offset; narrowOop str; }, которому предшествует хеш-значение, за которым следует смещение или указатель символьного объекта.
  8. Два разных типа структур могут быть просто показаны следующим образом: первое ведро и третье ведро являются общими типами, указывая на множество записей, состоящих из [хэш + смещение], в то время как второе ведроVALUE_ONLY_BUCKET_TYPEвведите, указывая прямо на [смещение].
buckets[0, 4, 5, ....]
        |  |  |
        |  |  +---+
        |  |      |
        |  +----+ |
        v       v v
entries[H,O,H,O,O,H,O,H,O.....]
template <class T, class N>
inline T CompactHashtable<T,N>::lookup(const N* name, unsigned int hash, int len) {
  if (_entry_count > 0) {
    int index = hash % _bucket_count;
    u4 bucket_info = _buckets[index];
    u4 bucket_offset = BUCKET_OFFSET(bucket_info);
    int bucket_type = BUCKET_TYPE(bucket_info);
    u4* entry = _entries + bucket_offset;

    if (bucket_type == VALUE_ONLY_BUCKET_TYPE) {
      T res = decode_entry(this, entry[0], name, len);
      if (res != NULL) {
        return res;
      }
    } else {
      u4* entry_max = _entries + BUCKET_OFFSET(_buckets[index + 1]);
      while (entry < entry_max) {
        unsigned int h = (unsigned int)(entry[0]);
        if (h == hash) {
          T res = decode_entry(this, entry[1], name, len);
          if (res != NULL) {
            return res;
          }
        }
        entry += 2;
      }
    }
  }
  return NULL;
}

Логика добавления хеш-таблицы следующая:

  1. Используются ли другие хэш-алгоритмы, и если да, пересчитайте значение хеш-функции и рассчитайте соответствующее значение индекса.
  2. пройти черезlookup_in_main_tableФункция проверяет, существует ли строковое значение в хэш-таблице.
  3. Создать запись, включая хеш-значение и указатель строкового объекта.
  4. пройти черезadd_entryфункция добавляется в хеш-таблицу.
  5. Возвращает строковый объект.
oop StringTable::basic_add(int index_arg, Handle string, jchar* name,
                           int len, unsigned int hashValue_arg, TRAPS) {

  NoSafepointVerifier nsv;
  unsigned int hashValue;
  int index;
  if (use_alternate_hashcode()) {
    hashValue = alt_hash_string(name, len);
    index = hash_to_index(hashValue);
  } else {
    hashValue = hashValue_arg;
    index = index_arg;
  }
  oop test = lookup_in_main_table(index, name, len, hashValue); 
  if (test != NULL) {
    return test;
  }
  HashtableEntry<oop, mtSymbol>* entry = new_entry(hashValue, string());
  add_entry(index, entry);
  return string();
}

-XX:StringTableSize

Как упоминалось ранее, размер сегмента хеш-таблицы JVM по умолчанию: 60013 для 64-битных систем и 1009 для 32-битных систем. Если мы хотим изменить его размер, мы можем установить-XX:StringTableSizeдля достижения эффекта.

-XX:+PrintStringTableStatistics

Если вы хотите увидеть статистику, связанную с постоянным пулом, можно установить-XX:+PrintStringTableStatistics,那么 JVM 停止时就会输出相关信息了。 Например,

SymbolTable statistics:
Number of buckets       :     20011 =    160088 bytes, avg   8.000
Number of entries       :     20067 =    481608 bytes, avg  24.000
Number of literals      :     20067 =    838520 bytes, avg  41.786
Total footprint         :           =   1480216 bytes
Average bucket size     :     1.003
Variance of bucket size :     0.994
Std. dev. of bucket size:     0.997
Maximum bucket size     :         8
StringTable statistics:
Number of buckets       :     60013 =    480104 bytes, avg   8.000
Number of entries       :   1003077 =  24073848 bytes, avg  24.000
Number of literals      :   1003077 =  48272808 bytes, avg  48.125
Total footprint         :           =  72826760 bytes
Average bucket size     :    16.714
Variance of bucket size :     9.683
Std. dev. of bucket size:     3.112
Maximum bucket size     :        30

------------- Рекомендуем прочитать ------------

Резюме моей статьи за 2017 год — машинное обучение

Краткое изложение моих статей за 2017 год — Java и промежуточное ПО

Резюме моих статей 2017 года — глубокое обучение

Краткое изложение моих статей за 2017 год — исходный код JDK

Резюме моей статьи за 2017 год — обработка естественного языка

Резюме моих статей 2017 года — Java Concurrency

------------------рекламное время----------------

Поговори со мной, задай мне вопросы:

这里写图片描述

Меню официальной учетной записи было разделено на «распределенное», «машинное обучение», «глубокое обучение», «НЛП», «глубина Java», «ядро параллелизма Java», «исходный код JDK», «ядро Tomcat», и т.д. Там может быть один стиль, чтобы удовлетворить ваш аппетит.

Зачем писать «Анализ проектирования ядра Tomcat»

Добро пожаловать, чтобы следовать:

这里写图片描述