Я исследовал проблему с async-profiler , который может рисовать крутые графики пламени, показывающие, на что тратится время процессора.
Как отметил @AlekseyShipilev, замедление между JDK 8 и JDK 9 в основном является результатом изменений StackWalker. Также G1 стал GC по умолчанию с JDK 9. Если мы явно установим -XX:+UseParallelGC
(по умолчанию в JDK 8), оценки будут немного лучше.
Но самая интересная часть - это замедление в JDK 11.
Вот что показывает асинхронный профилировщик (кликабельный SVG).
![JDK 10](https://i.stack.imgur.com/zmgrn.png)
![JDK 11](https://i.stack.imgur.com/6X9rd.png)
Основное различие между двумя профилями заключается в размере блока java_lang_Throwable::get_stack_trace_elements
, в котором преобладает StringTable::intern
. Очевидно, StringTable::intern
занимает намного больше времени на JDK 11.
Давайте увеличим:
![JDK 11 zoom in](https://i.stack.imgur.com/2C307.png)
Обратите внимание, что StringTable::intern
в JDK 11 вызывает do_intern
, что, в свою очередь, выделяет новый java.lang.String
объект. Выглядит подозрительно Ничего подобного не видно в профиле JDK 10. Время искать в исходном коде.
stringTable.cpp (JDK 11)
oop StringTable::intern(Handle string_or_null_h, jchar* name, int len, TRAPS) {
// shared table always uses java_lang_String::hash_code
unsigned int hash = java_lang_String::hash_code(name, len);
oop found_string = StringTable::the_table()->lookup_shared(name, len, hash);
if (found_string != NULL) {
return found_string;
}
if (StringTable::_alt_hash) {
hash = hash_string(name, len, true);
}
return StringTable::the_table()->do_intern(string_or_null_h, name, len,
| hash, CHECK_NULL);
} |
----------------
|
v
oop StringTable::do_intern(Handle string_or_null_h, const jchar* name,
int len, uintx hash, TRAPS) {
HandleMark hm(THREAD); // cleanup strings created
Handle string_h;
if (!string_or_null_h.is_null()) {
string_h = string_or_null_h;
} else {
string_h = java_lang_String::create_from_unicode(name, len, CHECK_NULL);
}
Функция в JDK 11 сначала ищет строку в общей StringTable, не находит ее, затем переходит к do_intern
и сразу же создает новый объект String.
В источниках JDK 10 после вызова lookup_shared
произошел дополнительный поиск в главной таблице, который возвратил существующую строку без создания нового объекта:
found_string = the_table()->lookup_in_main_table(index, name, len, hashValue);
Этот рефакторинг был результатом JDK-8195097"Сделать возможным обработку StringTable вне безопасной точки".
TL; DR При интернировании имен методов в JDK 11 HotSpot создает избыточные объекты String. Это произошло после JDK-8195097 .