(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет!

Нашел отличное видео по исключениям в Java https://www.youtube.com/watch?v=UIbbNsta2UE

Краткий конспект, не влияющий на рекомендацию посмотреть видео:
1) затраты на выбрасывание исключений конечно же есть, но если исключение в вашем сервисе равно ошибке, то проблем с производительностью не будет. Т.к. ошибки как правило не выбрасываются сотнями и тысячами в секунду
2) если ваши исключение выбрасывается в строго определенном месте кода - можно убрать из него stack trace, это неплохо так увеличит производительность. На самом деле если просто не обращаться к stack trace, то она уже увеличится, но для надежности лучше вообще не прикреплять stack trace к исключению. Или использовать StackWalking API https://www.baeldung.com/java-9-stackwalking-api
3) самый спорный и опасный совет для того же кейса - исключение, выбрасываемое в одном месте - закэшировать исключение, так его выброс будет еще быстрее. Но по сути это старый добрый go to. Использовать с осторожностью!)
4) исключение должно содержать весь контекст ошибки, в идеале с предложениями по ее исправлению. Чтобы структурировать информацию об ошибке есть библиотека https://github.com/melix/jdoctor Активность в репозитории слабенькая, но сама идея мне нравится.
5) как известно, есть исключения, которые не стоит ловить - например, OutOfMemory и StackOverflow. Но если очень надо, то OutOfMemory все же можно) Главное - заранее создать все необходимые для сохранения информации о проблеме объекты, ведь после появления OutOfMemory памяти уже не будет.

А еще из интересного - после просмотра видео станет понятно, как работает SneakyThrows в Lombok

#java #exceptions
Всем привет!

Продолжаю серию полезных видео - https://youtu.be/j-i3NQiKbcc
Тут по полочкам расписывает как работает логирование в Java.

Краткий конспект по архитектуре логирования:
1) адаптер - предоставляет API, которое вызывается из кода. На данный момент их 3 - SL4J, JCL (Apache Common Logging) и JBoss Logging. Самый распространенный и рекомендуемый - SLF4J
2) bridge - нужен, когда какая-то библиотека использует не тот адаптер, что мы хотим. По сути адаптер на адаптер, который эмулирует API, вызываемое из кода, и пробрасывает вызовы в нужный адаптер, как правило slf4j. Понятно, что когда у нас есть адаптер на адаптер, есть риск бесконечной рекурсии. Про это надо помнить)
3) движок логгера - компонента, которая пишет логи. Примеры: log4j, log4j2, logback, JUL\JDK (встроенный в JDK)
4) appender - компонент, определяющий физическое место, куда пишутся логи: консоль, диск, БД, MQ... Вот полный список для log4j2 https://logging.apache.org/log4j/2.x/manual/appenders.html
5) фильтры и конверторы - позволяют отфильтровать или преобразовать сообщения на клиенте

Плюс 3 хороших совета из видео:
1) соблюдать гигиену classpath - чистить его от лишних библиотек
2) логи в теории могут стать основой мониторинга, когда мы отбрасываем специальным образом размеченную запись в лог, которая после обработки лога становится событием мониторинга
3) не добавлять как зависимость в свои библиотеки движок логгера. Пусть его выберет потребитель, а не разбирается с вашими транзитивными зависимостями

И 2 полезные утилиты - миграторы на logback и slf4j с альтернативных библиотек логирования.

Из минусов вижу пожалуй один - там идет рассказ о связке slf4j и logback. Если во время создания видео logback сильно обогнал log4j, то сейчас с log4j2 ситуация меняется. Неплохо бы добавить сравнение с log4j2.

#java #logging
Всем привет!

Какие компиляторы есть в Java? Сколько их вообще?
Простой ответ - один, называется javac. Им мы компилируем исходники в байт-код, который потом исполняет JVM.
Исполняет и что важно оптимизирует.
Вообще говоря, основные оптимизации происходят именно runtime, а javac является довольно простым.
Идея в том, что собирается статистика использования кода во время выполнения, часто используемый код компилируется в "нативный" - ассемблер для конкретного процессора, а неиспользуемый - удаляется. Таким образом получаем плюс еще один компилятор - JIT, он же Just in Time. Только на самом деле, исторически, компиляторов два - один быстрый, но оптимизирующий не оптимально))), второй - наоборот, медленный и хорошо оптимизирующий. C1 и C2, сейчас они используются в паре, подробнее можно почитать тут https://for-each.dev/lessons/b/-jvm-tiered-compilation

А можно без байт-кода? Да, есть AOT - Ahead of Time компилятор, называется native-image, поставляется в GraalVM https://www.graalvm.org/latest/reference-manual/native-image/
Он сразу компилирует в требуемый "нативный" код.
А если поддержки требуемой процессорной архитектуры нет? Был момент доминирования x86, но сейчас растет популярность ARM архитектур, а там, я так понимаю, тот еще зоопарк.
А для этого уже существует промежуточный язык и набор компиляторов LLVM https://llvm.org/. Что-то типа Java байт-кода, только не привязанный к Java. GraalVM его тоже поддерживает https://www.graalvm.org/latest/reference-manual/native-image/LLVMBackend/
А можно его использовать как runtime компилятор? Почему нет, в Azul JDK решили отказаться от C1\C2 и сделать свой компилятор с блекджеком и LLVM - https://www.azul.com/products/components/falcon-jit-compiler/ Да, ошибся немного, блекджека там, увы, нет)

А еще есть компиляторы Kotlin, Scala, Groovy, Jython, JRuby... И Kotlin native, также использующий LLVM https://kotlinlang.org/docs/native-overview.html В общем я сбился со счета)

#java #jvm #jdk #native_image #compilers
Всем привет!

Продолжу серию постов https://t.me/javaKotlinDevOps/269 про оптимизацию производительности Java приложения.
В первых двух частях я говорил про такие технологии как:
1) native image - компиляция в нативный код на этапе сборки, т.об. устраняется необходимость class loading-а и JIT компиляции
2) CRaC - сохраняет и восстанавливает состояние работающего Docker образа с JRE на диск, т.об. мы получаем уже оптимизированный код

Какие еще могут быть способы выйти на оптимальную производительность побыстрее? native image мы пока отбрасываем, у нас обычная JVM и на ней запускается байт-код.
Встречный вопрос - а что мешает достижению оптимальной производительности? Как ни странно - JIT компилятор. Ведь чтобы ему понять, как оптимизировать байт-код, нужно собрать статистику. Причем процесс сбора статистики может быть цикличным - собрали, оптимизировали, поняли что оптимизация неверная, вернули байт-код обратно... И это все требует времени. А почему бы тогда не собрать статистику по использованию кода заранее, прихранить ее куда-нибудь, а потом использовать сразу со старта.
Эта техника называется Profile-Guided Optimization, в нее умеет GraalVM https://www.graalvm.org/latest/reference-manual/native-image/optimizations-and-performance/PGO/basic-usage/ и упоминаемая ранее Azul JDK https://docs.azul.com/prime/Use-ReadyNow Но к сожалению оба - только в коммерческой версии.
Еще похожую технику использует стандартная OpenJDK при tired compilation https://for-each.dev/lessons/b/-jvm-tiered-compilation но в данном случае речь идет про отпимизацию в течение одной рабочей сессии.

P.S. Это еще не все возможные варианты, не переключайтесь)

P.P.S. Может возникнуть вопрос - зачем GraalVM использует профилирование, он же и так все оптимизировал? Нет, не все. На этапе компиляции нет информации об реальном использовании кода. А оптимизация - это не только компиляция в нативный код, это еще может быть выбрасывание лишних проверок, разворачивание цикла и т.д.

#jre #performance #java_start_boost
Всем привет!

Продолжим рассказ про разные способы ускорения Java. Для начала я бы разделил ускорение в целом на 4 более конкретных направления:
1) ускорение запуска приложения за счет оптимизации\отмены первоначальной загрузки классов
2) ускорение выхода приложения на оптимальную производительность путем оптимизации JIT - Just In Time - компиляции байт-кода в нативный
3) ускорение запуска и в какой-то степени выполнения приложения за счет более легковесного фреймворка, используемого для разработки приложения
4) оптимизация сборщика мусора для достижения нужного баланса между затрачиваемыми ресурсами и паузой в обслуживании клиентских запросов, она же Stop the World

Сегодня поговорим про первое направление. С одной стороны упомянутые ранее и native image, и CRaC тоже ускоряют запуск. Но обе технологии имеют ограничения. native image запрещает reflection и динамическую загрузку классов. Образ, сохраненный с помощью CRaC, может содержать что-то лишнее, и с данной технологией нельзя просто так перезапустить приложение при сбое - т.к. возможно причина сбоя лежит в данных, подгруженные из образа.

Начну издалека.
В Java 5 появилась вот такая фича - https://docs.oracle.com/en/java/javase/21/vm/class-data-sharing.html Class-Data Sharing, сокращенно CDS.
Фича появилась и была забыта. Есть такие фичи, про которые все забывают сразу после релиза новой Java) Еще модульность из Java 9 можно вспомнить.

О чем эта фича? Мы записываем в файл метаданные загруженных классов из classpath. Потом этот файл мапился в память работающей JVM. Зачем? Цели было две:
1) расшаривание классов между несколькими инстансами JVM и т.об. уменьшение потребления RAM
2) ускорение запуска (вот оно!)

Вначале фича работала только с классами Java core. Файл с архивом классов Java core входит в состав JDK, найти его можно по имени classes.jsa. Занимает на диске сравнительно немного - 10-15 Мб. И кстати, CDS в Java включена по умолчанию, используется как раз этот файл.

Позже, в Java 10 https://openjdk.org/jeps/310 появилась возможность дампить и пользовательские классы, эту фичу назвали AppCDS. В Java 13 создание архива было упрощено https://openjdk.org/jeps/350
Пользовательские классы можно добавить в архив предварительно запустив процесс со специальной опцией командной строки -XX:ArchiveClassesAtExit

А если у нас Spring? Ребята в Spring 6.1 обратили внимание на данную опцию и добавили ключ командной строки, позволяющий собрать информацию о динамически загружаемых классах именно для Spring Boot приложения https://docs.spring.io/spring-framework/reference/integration/cds.html
А еще дали рекомендации, как максимально точно собрать информацию о классах и подтвердили, что данная опция ускоряет загрузку на ~30% https://spring.io/blog/2023/12/04/cds-with-spring-framework-6-1 Почему подтвердили - именно такую цель ставили разработчики CDS в JEP 310, упомянутом выше.

Итого - идея в чем-то похожа на Profile-Guided Optimization. Только здесь мы предварительно собираем информацию не об использовании кода, а о загруженных классах. Чем больше информации соберем - тем быстрее будет старт приложения. Минусы - версия JDK, Spring и classpath в целом должны совпадать при тестовом прогоне и использовании в ПРОМе.


#jre #performance #spring_boot #spring #java_start_boost