Да, метод sun.nio.ch.FileChannelImpl.map0 специфичен для Oracle JDK.
sun.misc.Unsafe тоже не является частью публичного API, но поддерживается и другими JVM.
У memcached нет причин обрабатывать больше запросов:
— аллокация у него медленнее (SLAB vs. pointer increment);
— поиск медленнее (hash + linear search vs. hash + binary search);
— копирование данных медленнее (socket i/o vs. memcpy).
Сейчас ни во что не упираемся — во время эксперимента весь трафик смайликов и подарочков прокачивался через один сервер. CPU при этом был загружен на 50%.
Nginx была идея использовать, но потребовалось бы дописать клиентский модуль для похода в хранилище, ведь все свое ПО разрабатываем на Java. А memcached даже не рассматривался, т.к. out-of-process и будет заведомо медленнее.
Да, в сишном интерпретаторе много что может отсутствовать. И работает он раза в 2-3 медленнее, чем template interpreter. А вообще внутренности можно изучать по исходникам C1 компилятора — они простые для понимания, но вместе с тем это живой и актуальный код. Например, захват монитора — C1_MacroAssembler::lock_object(), освобождение — C1_MacroAssembler::unlock_object().
Исходники интерпретатора лежат в папочке hotspot/src/share/vm/interpreter…
Следующим представляет интерес файл bytecodeInterpreter.cpp.
Это сишный интерпретатор для экспериментов и быстрого портирования на новые архитектуры. В production он не используется. На практике работает ассемблерный так называемый Template Interpreter. Его исходники лежат в src/cpu/x86/vm/templateTable* и src/cpu/x86/vm/templateInterpreter*.
Ха! Из таких «авторитетных» статей и рождаются мифы о Java. Поменяем в тесте строчки usingReflection() и usingMethodHandles() местами и… о чудо! теперь Reflection работает гораздо быстрее MethodHandles :)
// Там в комментариях к статье автору указали на ошибку. А Reflection на деле все-таки быстрее.
Наверное, вы так и сделали, не дочитав до места, где поясняется, в каком смысле сервер «асинхронный» :) Сервер, базирующийся на NIO, очень даже можно назвать асинхронным, поскольку он, как правило, эксплуатирует событийно-управляемую модель ввода-вывода. Кстати, MINA и Netty определяются как «asynchronous event-driven network application framework», несмотря на то, что ни тот, ни другой async I/O не используют.
Как бы то ни было, суть вопроса не в словах. Исходя из личного и чужого опыта, я взял на себя смелость возразить на утверждение, что «NIO по определению быстрее Blocking IO».
То, что NIO быстрее — довольно распространенный миф. В этой презентации, ссылаясь на эксперименты, автор показывает, что в действительности NIO уступает в производительности thread-per-socket решению примерно на 25%. К тем же результатам привели мои собственные опыты, где в высоконагруженной системе (~2K connections, ~30K requests per second) сервер на Netty показал пропускную способность на 15-35% ниже, чем простой сервер на блокирующих сокетах. Кроме того, за счет активного создания новых ChannelBuffer'ов, Netty-сервер оказывал заметно бОльшую нагрузку на потребление памяти и GC. В данном контексте единственным преимуществом NIO-сервера над thread-per-socket решением является меньшее число потоков и, как следствие, большее число коннекций, которое способен обслуживать один сервер.
По-моему, это и есть гадание. Один «magic» чего стоит. Правдивую информацию можно почерпнуть в исходниках, благо, что открытые.
Заголовок Java-объекта, действительно, состоит из 2х слов.
Первое — markOop — многофункциональное хранилище разнообразной информации об объекте.
В зависимости от ситуации может содержать
— хеш-код объекта;
— возраст (количество пережитых GC);
— lock (обычный, рекурсивный или biased).
Подробное описание с побитовыми схемами — в комментариях к markOop.hpp.
Второе слово — указатель на класс. Но не тот, который java.lang.Class, а klassOop — нативное описание типа объекта. Что из себя представляет Klass, можно вычитать, опять же, в комментариях к klass.hpp. В частности, Klass содержит и java_mirror — ссылку на java.lang.Class.
У массивов сразу за этими двумя словами заголовка идет 32-битная длина массива — см. arrayOop.hpp.
Что касается полей объекта, они переупорядочиваются для экономии занимаемого места с учетом выравнивания. long и double поля должны быть выровнены по 64-битной границе, int и float — по 32-битной, short и char — по 16-битной, а затем уже и byte с boolean. Для некоторых системных классов (например, String, Throwable, Reference), с которыми тесно взаимодействует VM, уплотнение полей не применяется.
на x86 работа с volatile реализуется с помощью FENCE инструкций, которые значительно влияют на производительность
В HotSpot JVM на x86/amd64 запись volatile поля сопровождается инструкцией LOCK ADD [RSP], 0 что достаточно для эффективного membar'а вместо медленного MFENCE. Чтение volatile поля ничем не отличается от чтения обычного поля.
Что касается final полей, HotSpot с ними всегда работает как с обычными. Никаких дополнительных действий для обеспечения вышеупомянутой семантики final виртуальная машина не осуществляет, ровно по той причине, что на всех ныне поддерживаемых архитектурах (x86, SPARC, ARM, PPC), пример ReorderingSample никогда не свалится. Случая с Reflection это не касается, поскольку он относится с Class Libraries и реализован за пределами JVM.
final поля и Reflection
Запись final или volatile полей через Reflection, действительно, сопровождается memory-barrier'ом. Поэтому все требования JMM в этом случае тоже соблюдены.
Убедимся в этом, заглянув в исходники sun.reflect.*
// ----- UnsafeFieldAccessorFactory.java -----
boolean isQualified = isFinal || isVolatile;
...
if (!isQualified) {
return new UnsafeObjectFieldAccessorImpl(field);
} else {
return new UnsafeQualifiedObjectFieldAccessorImpl(field, isReadOnly);
}
// ----- UnsafeObjectFieldAccessorImpl.java -----
public void set(Object obj, Object value) {
if (isFinal) {
throwFinalFieldIllegalAccessException(value);
}
...
unsafe.putObject(obj, fieldOffset, value);
}
// ----- UnsafeQualifiedObjectFieldAccessorImpl.java -----
public void set(Object obj, Object value) {
if (isReadOnly) {
throwFinalFieldIllegalAccessException(value);
}
...
unsafe.putObjectVolatile(obj, fieldOffset, value);
}
В свою очередь unsafe.putObjectVolatile(obj, fieldOffset, value) эквивалентен
sun.misc.Unsafe тоже не является частью публичного API, но поддерживается и другими JVM.
— аллокация у него медленнее (SLAB vs. pointer increment);
— поиск медленнее (hash + linear search vs. hash + binary search);
— копирование данных медленнее (socket i/o vs. memcpy).
Сейчас ни во что не упираемся — во время эксперимента весь трафик смайликов и подарочков прокачивался через один сервер. CPU при этом был загружен на 50%.
Это сишный интерпретатор для экспериментов и быстрого портирования на новые архитектуры. В production он не используется. На практике работает ассемблерный так называемый Template Interpreter. Его исходники лежат в src/cpu/x86/vm/templateTable* и src/cpu/x86/vm/templateInterpreter*.
// Там в комментариях к статье автору указали на ошибку. А Reflection на деле все-таки быстрее.
Как бы то ни было, суть вопроса не в словах. Исходя из личного и чужого опыта, я взял на себя смелость возразить на утверждение, что «NIO по определению быстрее Blocking IO».
на
Т.е. заинлайнить SHA1-дайджест прямо в поля класса. Отсюда экономия на лишнем объекте byte[] и ссылке на него.
Заголовок Java-объекта, действительно, состоит из 2х слов.
Первое — markOop — многофункциональное хранилище разнообразной информации об объекте.
В зависимости от ситуации может содержать
— хеш-код объекта;
— возраст (количество пережитых GC);
— lock (обычный, рекурсивный или biased).
Подробное описание с побитовыми схемами — в комментариях к markOop.hpp.
Второе слово — указатель на класс. Но не тот, который java.lang.Class, а klassOop — нативное описание типа объекта. Что из себя представляет Klass, можно вычитать, опять же, в комментариях к klass.hpp. В частности, Klass содержит и java_mirror — ссылку на java.lang.Class.
У массивов сразу за этими двумя словами заголовка идет 32-битная длина массива — см. arrayOop.hpp.
Что касается полей объекта, они переупорядочиваются для экономии занимаемого места с учетом выравнивания. long и double поля должны быть выровнены по 64-битной границе, int и float — по 32-битной, short и char — по 16-битной, а затем уже и byte с boolean. Для некоторых системных классов (например, String, Throwable, Reference), с которыми тесно взаимодействует VM, уплотнение полей не применяется.
В HotSpot JVM на x86/amd64 запись volatile поля сопровождается инструкцией LOCK ADD [RSP], 0 что достаточно для эффективного membar'а вместо медленного MFENCE. Чтение volatile поля ничем не отличается от чтения обычного поля.
Что касается final полей, HotSpot с ними всегда работает как с обычными. Никаких дополнительных действий для обеспечения вышеупомянутой семантики final виртуальная машина не осуществляет, ровно по той причине, что на всех ныне поддерживаемых архитектурах (x86, SPARC, ARM, PPC), пример ReorderingSample никогда не свалится. Случая с Reflection это не касается, поскольку он относится с Class Libraries и реализован за пределами JVM.
Запись final или volatile полей через Reflection, действительно, сопровождается memory-barrier'ом. Поэтому все требования JMM в этом случае тоже соблюдены.
Убедимся в этом, заглянув в исходники sun.reflect.*
В свою очередь unsafe.putObjectVolatile(obj, fieldOffset, value) эквивалентен