Как стать автором
Обновить

Комментарии 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 оно умеет, бинари получаются не очень жирные - одни плюсы. Единственный минус - нет кросс-компиляции, вообще никакой.

Вполне возможно, что на EE версии не будет просадки производительности. Получается что в комьюнити версии еще стандартной диагностики не сделать, GC не сменить.

Еще интересно, как эти сегфолты перехватываются. В линуксе, наверное, через signal() и longjmp() - но и то сомнительно. А на остальных платформах как? Уж шибко низкоуровнево… неужели можно везде без залезания в ядро это поймать?

SEH на винде.

Вообще если для платформы есть C++, то там уже все есть, потому что исключения крестов сделаны по таким же принципам.

Не пишу на 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, которые вы используете — будь приватные или нет

Зарегистрируйтесь на Хабре, чтобы оставить комментарий