Комментарии 18
Статья 🔥
Приятно, когда организовывают такую экскурсию в подземелье jvm.
Давным-давно, ещё в Windows 7, тоже как-то для любопытства создал эту godmode папку, и вроде забыл про неё. А через некоторое время стал замечать, что в Eclipse диалоги открытия файлов перестали отображать файлы с точкой в начале. Обгуглился весь, намучался, не мог понять отчего. А потом "случайно" папку удалил - всё стало нормально.
Одним из главных преимуществ JIT-компилируемых языков называют адаптивную кроссплатформенность - даже старый код может работать на новых платформах, используя все их новейшие возможности. Например, если JVM работает на системе с поддержкой AVX-512, то она оптимизирует узкие места в коде с помощью этих инструкций. Насколько это соответствует реальности? Вообще, возможен (и перспективен ли) глубокий анализ циклов сложнее a=b+c в рантайме? Планируется ли в Java кроссплатформенная векторная библиотека, как в C#?
AVX-512 это сомнительная штука со всех точек зрения. С учетом что надо чтобы хорошо работало везде. Особенно в контейнерах на серверах.
Векторная библиотека в превью. Бонус производительности небольшой выходит на самом деле. https://openjdk.org/jeps/426
А все остальное есть и работает. Рантайм оптимизаций много и разных. И они заметно сложнее чем анализ простых циклов.
Вопрос про NullPointerException.
В примере с `t.a = 42` получаем SEGFAULT, потому что смещение 0xC для поля a
довольно маленькое.
Если бы наша структура `Test` была супер-развесистой, и мы пробуем писать в ее последнее поле. Есть ли вероятность в таком случае попасть в валидный адрес памяти и прочитать значение (какое-то) без SEGFAULT? Если да - как JVM обрабатывает такую ситауцию, чтобы все же выкинуть NPE в итоге?
Хорошая статья, после прочтения грустно становится, что занимаешься какой-то ерундой, а не пишешь JVM. Всегда было любопытно как сегфолты заменяются NPE, дать развалиться, а потом разбираться что пошло не так - смело.
Интересно, кто-то уже использует Native Image в продакшене, кажется, оно еще сыровато.
Использую в личных проектах.
В целом - да, сыровато, главная проблема прежде всего в производительности - сервис на Micronaut выдаёт в два раза меньше rps после компиляции GraalVM. Иногда приходится шаманить с reflect-config.json и прочей метаинформацией, чтобы, например, завести логгер.
Но из коробки доступен metadata repository, плагины (для котлина, например), agent, который собирает информацию про рефлексию и тд-тп.
Но как же приятно делать на нем клиентские приложения... Java Swing оно умеет, бинари получаются не очень жирные - одни плюсы. Единственный минус - нет кросс-компиляции, вообще никакой.
Еще интересно, как эти сегфолты перехватываются. В линуксе, наверное, через signal() и longjmp() - но и то сомнительно. А на остальных платформах как? Уж шибко низкоуровнево… неужели можно везде без залезания в ядро это поймать?
Не пишу на Java, но было крайне интересно почитать. Было бы здорово провести параллели с .NET, как они там справлялись с теми же проблемами. Особенно в части synchronized/lock: когда-то попадалось, что в CLR в каждом объекте есть выделенные байты для реализации функционала монитора.
ты используешь java и я использую java, но есть один нюанс)
«У нас клиенты жалуются, что у них вот такой, казалось бы, очень простой код ни с того, ни с сего просел на новой джаве»
try {
int newByte = newContent.read();
int oldByte = oldContent.read();
while (newByte != = 1 && oldByte != -1 && newByte == oldByte) {
newByte = newContent.read();
oldByte = oldContent.read();
} contentChanged = newByte != oldByte;
} catch (IOException e) {
contentChanged = true;
}
ИМХО, вполне ожидаемо, ибо нечего побайтно из стримов (пусть даже и буферизировнных) читать. Тут и без рантайма на уровне самой джавы проблемы могут вылезти.
А можно подробнее разъяснить, пожалуйста?
Конечно, смотрим код BufferedInputStream
:
public int read() throws IOException {
if (lock != null) {
lock.lock();
try {
return implRead();
} finally {
lock.unlock();
}
} else {
synchronized (this) {
return implRead();
}
}
}
Получается, с отказом от привязанной блокировки мы теперь честно блокируемся при каждом вызове BufferedInputStream.read()
, что для рассматриваемого примера
try {
int newByte = newContent.read();
int oldByte = oldContent.read();
while (newByte != = 1 && oldByte != -1 && newByte == oldByte) {
newByte = newContent.read();
oldByte = oldContent.read();
}
contentChanged = newByte != oldByte;
} catch (IOException e) {
contentChanged = true;
}
означает двойную блокировку при каждом проходе цикла. Но что делает код выше? По сути, он побайтно сравнивает содержимое двух буферизированных стримов и если хотя бы один несовпадает, то contentChanged
становится истиной. Вместо побайтного чтения можно использовать, например, InputStream.readNBytes(int)
и читать сразу 1024 байта, например. Таким образом для их сравнения мы захватим блокировку дважды, а 2048 раз. Если мы точно знаем, что размер сравниванемых данных невелик можно использовать InputStream.readAllBytes()
, считав все байты за раз и сравнивая их без блокировок вообще. В этом случае может оказаться, что BufferedInputStream
вообще лишний. О таких примерах я когда-то рассказывал.
Ну а глобально вывод такой: приватные API лучше не использовать
Ну из этой истории по-моему вывод напрашивается другой — придерживайтесь контрактов API, которые вы используете — будь приватные или нет
Один день из жизни JVM-инженера