Всем привет! Меня зовут Денис, можно просто Дэн. Я пришёл в разработку 20 лет назад, когда ещё был жив Delphi, где-то через 3 года перешёл на Java. За это время успел покодить, побыть тимлидом, начальником отдела, ИТ-директором, техлидом и даже на пару лет отойти от Java. Каюсь) За это время накопилось немного опыта, мыслей, вопросов, идей, которыми хочется поделиться. Поэтому и решил завести канал про Java разработку и сопутствующие ей темы. Что здесь будет - да много чего :) В частности: сравнение технологий, может даже с холиваром, а ля Java vs Kotlin, Maven vs Gradle, Lombok или не Lombok. Еще: типичные проблемы при разработке и что с ними можно сделать, мысли про новые фреймворки, библиотеки, языки, и т.д. DevOps в части CI. Совсем базовых вещей здесь не будет, для этого есть Baeldung, куча бесплатных курсов и хороших книг
Послушал доклад на JPoint про уже подзабытую уязвимость log4j и удивился 3 вещам.
Напомню: оказывается некоторые версии log4j2 позволяли вставлять в выводимое в лог сообщение выражение, позволяющее сделать удалённый вызов через механизм JNDI lookup. Причём есть возможность не только слить какие-то критичные данные, но и что-то запустить на сервере, пишущем логи, через встроенный в JDK JS движок и его функцию eval.
Первое, что удивило: уязвимость появилась в 2014 году, найдена и обсуждена на конференции по ИБ в 2016 году. Примерно тогда же появился первый фикс, который ничего не фиксил) Выстрелила в конце 2021. Как можно было про нее забыть??? Неразвитость ИБ? Информационная перегрузка? Кому-то было выгодно?
Второе: на первый взгляд эксплуатировать уязвимость сложно - нужно чтобы совпали 3 фактора: использование log4j определённого диапазона версий, отсутствие валидации пользовательского ввода и либо возможность вызвать внешний сервер для передачи конфиденциальных данных или загрузки remote code из внутренней сети, либо сервер атакующего уже должен быть во внутренней сети. Ну и детали об атакуемой системе неплохо бы знать: куда передавать сообщение с exploit-ом, что можно слить, что можно запустить на сервере. Но если подумать, несколько широко распространён log4j, как много пользовательских данных, http заголовков к нам приходит, и что-то из этого точно логируется, а главное как много ИТ систем в мире и 100% не у всех хватает денег на выделенного ИБ спеца - уязвимость совсем не кажется абстрактной, скорее наоборот.
И наконец третье. Разные я видел способы пропатчить библиотеку, сам патчил, но чтобы взять и вырезать файл - это что новое. Но способ эффективный - учитывая редкость использования функционала, в котором была найдена уязвимость, появившийся на сервере ClassNotFoundException в 99,9% случаев будет указывать на атаку. Ну и напоследок - как можно было такое редкий и опасный функционал включать по умолчанию...
Вывод: следование практикам ИБ и feature toggling рулят
#security #vulnerability
Напомню: оказывается некоторые версии log4j2 позволяли вставлять в выводимое в лог сообщение выражение, позволяющее сделать удалённый вызов через механизм JNDI lookup. Причём есть возможность не только слить какие-то критичные данные, но и что-то запустить на сервере, пишущем логи, через встроенный в JDK JS движок и его функцию eval.
Первое, что удивило: уязвимость появилась в 2014 году, найдена и обсуждена на конференции по ИБ в 2016 году. Примерно тогда же появился первый фикс, который ничего не фиксил) Выстрелила в конце 2021. Как можно было про нее забыть??? Неразвитость ИБ? Информационная перегрузка? Кому-то было выгодно?
Второе: на первый взгляд эксплуатировать уязвимость сложно - нужно чтобы совпали 3 фактора: использование log4j определённого диапазона версий, отсутствие валидации пользовательского ввода и либо возможность вызвать внешний сервер для передачи конфиденциальных данных или загрузки remote code из внутренней сети, либо сервер атакующего уже должен быть во внутренней сети. Ну и детали об атакуемой системе неплохо бы знать: куда передавать сообщение с exploit-ом, что можно слить, что можно запустить на сервере. Но если подумать, несколько широко распространён log4j, как много пользовательских данных, http заголовков к нам приходит, и что-то из этого точно логируется, а главное как много ИТ систем в мире и 100% не у всех хватает денег на выделенного ИБ спеца - уязвимость совсем не кажется абстрактной, скорее наоборот.
И наконец третье. Разные я видел способы пропатчить библиотеку, сам патчил, но чтобы взять и вырезать файл - это что новое. Но способ эффективный - учитывая редкость использования функционала, в котором была найдена уязвимость, появившийся на сервере ClassNotFoundException в 99,9% случаев будет указывать на атаку. Ну и напоследок - как можно было такое редкий и опасный функционал включать по умолчанию...
Вывод: следование практикам ИБ и feature toggling рулят
#security #vulnerability
(java || kotlin) && devOps pinned «Всем привет! Меня зовут Денис, можно просто Дэн. Я пришёл в разработку 20 лет назад, когда ещё был жив Delphi, где-то через 3 года перешёл на Java. За это время успел покодить, побыть тимлидом, начальником отдела, ИТ-директором, техлидом и даже на пару лет…»
Чем kubernetes, он же k8s лучше контейнера сервлетов или сервера приложений.
Во-первых под капотом k8s лежит Docker, а значит мы получаем все его плюшки. Не зря k8s называют оркестратором контейнеров. Чем занимается оркестратор?
1) планированиеи ресурсов. Разработчик декларативно задает как он хочет задеплоить своё приложение в Deployment. А именно: сколько нужно экземпляров - replicas, сколько CPU и памяти - requests и limits, про соседству с какими приложениями его нужно размещать, по соседству с какими не нужно - labels и (anti) affinity. k8s исходя из имеющихся у него серверов, они же nodes, и их загрузки размещает экземпляры приложения - pods. Или процесс раскатки падает, если подходящих серверов нет( У серверов приложений такого механизма нет. Важно добавить - процесс поддержания требуемой конфигурации приложения непрерывный, при любых отклонениях k8s будет убивать и добавлять поды.
2) стратегия раскатки. k8s позволяет в том же Deployment в параметре strategy задать два базовых варианта: при появлении новой версии гасить все поды сразу и поднимать новые или раскатывать плавно, учитывая значения максимально допустимого превышения и нехватки числа экземпляров MaxSurge и MaxUnaveilable. Для серверов приложений такое поведение можно реализовать с помощью DevOps джобы.
3) автоматическое восстановление. Нода или под могут упасть. Падение определяется по healthcheck, заданному в том же Deployment, причём очень гибко, см livenessProbe, readinessProbe и startupProbe. k8s при наступлении такого события рестартует pod. Для серверов приложений это либо делается руками, либо автоматизируется извне.
4) горизонтальное масштабирование. С помощью HorizontalPodAutoscaller можно задать диапазон числа реплик и предельную загрузку CPU, при которой k8s добавляет или убирает реплики. Для серверов приложений - опять же ручное управление.
Отдельно хочу отменить внутреннюю DNS в k8s. Если у вас несколько приложений в одном облаке, он же cluster, то можно делать запросы по имени из Service. Вся внутренняя маршрутизация в облаке работает именно так. Для серверов приложений снова ничего такого нет.
Ну и load balancing, url/port mapping и SSL termination. В старом мире этим занимался отдельно стоящий nginx, в k8s его включили в периметр системы. #cloud
Во-первых под капотом k8s лежит Docker, а значит мы получаем все его плюшки. Не зря k8s называют оркестратором контейнеров. Чем занимается оркестратор?
1) планированиеи ресурсов. Разработчик декларативно задает как он хочет задеплоить своё приложение в Deployment. А именно: сколько нужно экземпляров - replicas, сколько CPU и памяти - requests и limits, про соседству с какими приложениями его нужно размещать, по соседству с какими не нужно - labels и (anti) affinity. k8s исходя из имеющихся у него серверов, они же nodes, и их загрузки размещает экземпляры приложения - pods. Или процесс раскатки падает, если подходящих серверов нет( У серверов приложений такого механизма нет. Важно добавить - процесс поддержания требуемой конфигурации приложения непрерывный, при любых отклонениях k8s будет убивать и добавлять поды.
2) стратегия раскатки. k8s позволяет в том же Deployment в параметре strategy задать два базовых варианта: при появлении новой версии гасить все поды сразу и поднимать новые или раскатывать плавно, учитывая значения максимально допустимого превышения и нехватки числа экземпляров MaxSurge и MaxUnaveilable. Для серверов приложений такое поведение можно реализовать с помощью DevOps джобы.
3) автоматическое восстановление. Нода или под могут упасть. Падение определяется по healthcheck, заданному в том же Deployment, причём очень гибко, см livenessProbe, readinessProbe и startupProbe. k8s при наступлении такого события рестартует pod. Для серверов приложений это либо делается руками, либо автоматизируется извне.
4) горизонтальное масштабирование. С помощью HorizontalPodAutoscaller можно задать диапазон числа реплик и предельную загрузку CPU, при которой k8s добавляет или убирает реплики. Для серверов приложений - опять же ручное управление.
Отдельно хочу отменить внутреннюю DNS в k8s. Если у вас несколько приложений в одном облаке, он же cluster, то можно делать запросы по имени из Service. Вся внутренняя маршрутизация в облаке работает именно так. Для серверов приложений снова ничего такого нет.
Ну и load balancing, url/port mapping и SSL termination. В старом мире этим занимался отдельно стоящий nginx, в k8s его включили в периметр системы. #cloud
Всем привет! Решил написать пост про архитектурную проблему, с которой столкнулся недавно. Где делать сортировку данных? Обычно проблема звучит так: на клиенте, т.е в коде приложения, или на сервере, т.е. в БД? У нас все чуть сложнее. Дано:
1) БД
2) бэк-система
3) миддл-система, обслуживает веб-клиентов
4) веб-клиент.
Для начала я бы отбросил бэк-систему и веб-клиента. Веб-клиента - потому что в презентационном слое логики должно быть как можно меньше. При этом надо отметить, что исключения из этого правила могут быть, т.к клиенты сейчас мощные - 4+ ядра, 8+ Гб памяти. А бэк-систему исключаем потому, что с одной стороны рядом есть БД, а с другой - сортировка нужна для отображения пользователю, поэтому логичнее ее переместить поближе к месту использования. Т.е. сводим проблему к исходной - клиент (миддл) или сервер (БД). Исходя из каких аргументов можно сделать окончательный выбор:
1) является ли сортировка частью контракта, т.е API? Нужна ли она другим потребителям? Если является - то в БД
2) нужна ли пагинация? Если да - то сортируем в БД, т.к. только там есть полный набор данных
3) сложность условия сортировки. Можно ли его реализовать в SQL?
4) наличие индекса в БД и возможность его добавить. С индексом по полю сортировки в БД она в большинстве случае будет быстрее, чем на клиенте
5) число клиентов, обращающихся к БД. Если БД перегружена, а масштабирование невозможно, то имеет смысл вынести сортировку выше по цепочке. Еще важен тип БД, RDBMS можно масштабировать только горизонтально, 64 ядерные сервера дороги, а 128 ядерных не бывает)
6) может ли клиент менять способ сортировки при работе с данными? Если да, то стоит рассмотреть реализацию всех сортировок на клиенте
7) трудоемкость кодирования. В большинстве случаев тут "выигрывает" БД, т.к. ORDER BY декларативен и проще любых алгоритмов на Java. #arch
1) БД
2) бэк-система
3) миддл-система, обслуживает веб-клиентов
4) веб-клиент.
Для начала я бы отбросил бэк-систему и веб-клиента. Веб-клиента - потому что в презентационном слое логики должно быть как можно меньше. При этом надо отметить, что исключения из этого правила могут быть, т.к клиенты сейчас мощные - 4+ ядра, 8+ Гб памяти. А бэк-систему исключаем потому, что с одной стороны рядом есть БД, а с другой - сортировка нужна для отображения пользователю, поэтому логичнее ее переместить поближе к месту использования. Т.е. сводим проблему к исходной - клиент (миддл) или сервер (БД). Исходя из каких аргументов можно сделать окончательный выбор:
1) является ли сортировка частью контракта, т.е API? Нужна ли она другим потребителям? Если является - то в БД
2) нужна ли пагинация? Если да - то сортируем в БД, т.к. только там есть полный набор данных
3) сложность условия сортировки. Можно ли его реализовать в SQL?
4) наличие индекса в БД и возможность его добавить. С индексом по полю сортировки в БД она в большинстве случае будет быстрее, чем на клиенте
5) число клиентов, обращающихся к БД. Если БД перегружена, а масштабирование невозможно, то имеет смысл вынести сортировку выше по цепочке. Еще важен тип БД, RDBMS можно масштабировать только горизонтально, 64 ядерные сервера дороги, а 128 ядерных не бывает)
6) может ли клиент менять способ сортировки при работе с данными? Если да, то стоит рассмотреть реализацию всех сортировок на клиенте
7) трудоемкость кодирования. В большинстве случаев тут "выигрывает" БД, т.к. ORDER BY декларативен и проще любых алгоритмов на Java. #arch
Всем доброго вечера. Возвращаясь к теме k8s - может возникнуть вопрос: как его можно запустить на машине разработчика? Покрутить, поиграться с конфигами. Все-таки кластер, nodes, БД для хранения конфигурации, контроллеры, ingress proxy, администратор в придачу - выглядит страшно) Но есть альтернативы.
1) minikube. Упомянут в официальной документации k8s - https://kubernetes.io/docs/tutorials/hello-minikube/ Давно развивается. Поддерживает Win, Mac, Lin. Позволяет запустить k8s внутри VM, Docker образа или даже на голой операционке через механизм драйверов https://minikube.sigs.k8s.io/docs/drivers/ Последние два варианта запуска, увы, доступны только на Linux. Скорость запуска и требования к ресурсам при этом меняются от медленно и много к быстро и мало) Позволяет выбрать Docker или Podman. Позволяет подключать частные Docker репозитории или создать локальный. По умолчанию, как впрочем и все его соперники, стартует в режиме одной node, на которой расположены как служебные модули k8s, так и ваш код. Но можно и добавить node, и создать несколько кластеров. Есть web dashboard. kubectl - утилиту по управлению кластером через CLI API и VM гипервизор нужно ставить отдельно, но есть хорошая инструкция.
Подробнее https://minikube.sigs.k8s.io/docs/start/
2) kind. Работает поверх Docker Desktop, т.е. внутри Docker контейнера. Поддерживает Win, Mac, Lin. В случае Win есть возможность запуска без VM через WSL - https://docs.docker.com/desktop/windows/wsl/. Можно выбрать Docker образ с разными версиями k8s. Запускается достаточно быстро, хорошо совместим с CI системами. Позволяет настраивать частные и локальный Docker Registry, мультикластерную систему. kubectl и Docker Desktop нужно ставить отдельно.
https://kind.sigs.k8s.io/docs/user/quick-start/
3) Docker Desktop. Как ни странно включает в себя урезанный вариант k8s. Включается в админке Docker Desktop. Не кастомизируется. Все в одном - включает kubectl, не нужно ставить отдельно гипервизор. Хорош для начального знакомства.
https://docs.docker.com/desktop/kubernetes/
4) k3s. Интересно происхождение названия - если в слове k8s\kubernetes 10 символов, то k3s - 5, т.к. по задумке он должен потреблять в 2 раза меньше ресурсов. Запускается внутри Docker контейнера. Заточен под запуск на слабых машинах, в том числе ARM. Состоит из одного бинарника, из которого выкинуто все лишнее. Заточен под Lin, но вроде бы появилась поддержка Win. Но не Mac. Самый быстрый и легковесный. Хорошо совместим с CI системами. Позволяет подключать частные репозитории Docker образов. Для хранения конфигурации кластера по умолчанию использует SQLite, но через адаптер позволяет подключить внешнюю СУБД. Подчеркивает, что по умолчанию безопасно настроен. Позиционируется как production ready. Может запускаться как в режиме одной ноды, так и с несколькими, а также в режиме мультикластера.
https://k3s.io/ #cloud #ci
1) minikube. Упомянут в официальной документации k8s - https://kubernetes.io/docs/tutorials/hello-minikube/ Давно развивается. Поддерживает Win, Mac, Lin. Позволяет запустить k8s внутри VM, Docker образа или даже на голой операционке через механизм драйверов https://minikube.sigs.k8s.io/docs/drivers/ Последние два варианта запуска, увы, доступны только на Linux. Скорость запуска и требования к ресурсам при этом меняются от медленно и много к быстро и мало) Позволяет выбрать Docker или Podman. Позволяет подключать частные Docker репозитории или создать локальный. По умолчанию, как впрочем и все его соперники, стартует в режиме одной node, на которой расположены как служебные модули k8s, так и ваш код. Но можно и добавить node, и создать несколько кластеров. Есть web dashboard. kubectl - утилиту по управлению кластером через CLI API и VM гипервизор нужно ставить отдельно, но есть хорошая инструкция.
Подробнее https://minikube.sigs.k8s.io/docs/start/
2) kind. Работает поверх Docker Desktop, т.е. внутри Docker контейнера. Поддерживает Win, Mac, Lin. В случае Win есть возможность запуска без VM через WSL - https://docs.docker.com/desktop/windows/wsl/. Можно выбрать Docker образ с разными версиями k8s. Запускается достаточно быстро, хорошо совместим с CI системами. Позволяет настраивать частные и локальный Docker Registry, мультикластерную систему. kubectl и Docker Desktop нужно ставить отдельно.
https://kind.sigs.k8s.io/docs/user/quick-start/
3) Docker Desktop. Как ни странно включает в себя урезанный вариант k8s. Включается в админке Docker Desktop. Не кастомизируется. Все в одном - включает kubectl, не нужно ставить отдельно гипервизор. Хорош для начального знакомства.
https://docs.docker.com/desktop/kubernetes/
4) k3s. Интересно происхождение названия - если в слове k8s\kubernetes 10 символов, то k3s - 5, т.к. по задумке он должен потреблять в 2 раза меньше ресурсов. Запускается внутри Docker контейнера. Заточен под запуск на слабых машинах, в том числе ARM. Состоит из одного бинарника, из которого выкинуто все лишнее. Заточен под Lin, но вроде бы появилась поддержка Win. Но не Mac. Самый быстрый и легковесный. Хорошо совместим с CI системами. Позволяет подключать частные репозитории Docker образов. Для хранения конфигурации кластера по умолчанию использует SQLite, но через адаптер позволяет подключить внешнюю СУБД. Подчеркивает, что по умолчанию безопасно настроен. Позиционируется как production ready. Может запускаться как в режиме одной ноды, так и с несколькими, а также в режиме мультикластера.
https://k3s.io/ #cloud #ci
Kubernetes
Hello Minikube
This tutorial shows you how to run a sample app on Kubernetes using minikube. The tutorial provides a container image that uses NGINX to echo back all the requests.
Objectives Deploy a sample application to minikube. Run the app. View application logs. Before…
Objectives Deploy a sample application to minikube. Run the app. View application logs. Before…
Всем привет!
На данный момент для сборки Java/Kotlin проектов используются два основных инструмента: Maven и Gradle. Можно вспомнить Ant, сказать: «Покойся с миром» - и забыть) Их можно сравнивать по разным параметрам, но сегодня я хочу остановиться на инкрементальной сборке. Может возникнуть вопрос - что тут сравнивать, в Maven ее нет, в Gradle - есть. Но не все так очевидно) На самом деле в maven-compile-plugin такая опция заявлена https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html и даже включена по умолчанию. Но фактически не работает( Почему?
Для начала стоит разделить понятия сборки в целом и компиляции как одну из частей сборки. В Maven жизненный цикл сборки - это линейная последовательность фаз сборки, запуск каждой из которых не зависит от результата предыдущих фаз. В Gradle цикл - граф из task, а у каждой таски есть inputs и outputs, являющиеся файлами на диске. Т.к результат сборки в конечном итоге - файлы. Выходы одной таски являются входами другой. Если входы не изменились - таска пропускается. Причём проверка идёт по контрольной сумме, не по дате изменения. Т.об. если не менялся боевой код и код его теста, то тест не будет запущен повторно. В Maven этого нет в принципе.
По-разному работает и собственно компиляция. Если maven видит изменения в любом классе модуля или в модуле, от которого зависит текущий - идёт полная перекомпиляция модуля. И далее по цепочке( Gradle же во-первых умеет отслеживать зависимости по классам, а во-вторых проверять изменился ли интерфейс, application binary interface, ABI если быть точным, или только реализация, и в зависимости от этого перекомпилировать только нужные файлы. Кроме того в Gradle с появлением Java Library plugin появилась возможность разделять compile зависимости на api и implementation. Любые изменения в implementation зависимости не приводят к повторной компиляции использующих ее модулей.
Вывод: ребята из Gradle сильно заморочились скоростью сборки. Результаты можно увидеть тут: https://blog.gradle.org/incremental-compiler-avoidance. А ведь ещё есть Gradle Cache, от котором расскажу отдельно.
P.S. для Maven есть сторонний плагин, заменяющий 5 стандартных и предоставляющий настоящую инкрементальную сборку http://takari.io/book/40-lifecycle.html Честно скажу, не тестировал, смущает, что решение внешнее #buildtool #java #ci
На данный момент для сборки Java/Kotlin проектов используются два основных инструмента: Maven и Gradle. Можно вспомнить Ant, сказать: «Покойся с миром» - и забыть) Их можно сравнивать по разным параметрам, но сегодня я хочу остановиться на инкрементальной сборке. Может возникнуть вопрос - что тут сравнивать, в Maven ее нет, в Gradle - есть. Но не все так очевидно) На самом деле в maven-compile-plugin такая опция заявлена https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html и даже включена по умолчанию. Но фактически не работает( Почему?
Для начала стоит разделить понятия сборки в целом и компиляции как одну из частей сборки. В Maven жизненный цикл сборки - это линейная последовательность фаз сборки, запуск каждой из которых не зависит от результата предыдущих фаз. В Gradle цикл - граф из task, а у каждой таски есть inputs и outputs, являющиеся файлами на диске. Т.к результат сборки в конечном итоге - файлы. Выходы одной таски являются входами другой. Если входы не изменились - таска пропускается. Причём проверка идёт по контрольной сумме, не по дате изменения. Т.об. если не менялся боевой код и код его теста, то тест не будет запущен повторно. В Maven этого нет в принципе.
По-разному работает и собственно компиляция. Если maven видит изменения в любом классе модуля или в модуле, от которого зависит текущий - идёт полная перекомпиляция модуля. И далее по цепочке( Gradle же во-первых умеет отслеживать зависимости по классам, а во-вторых проверять изменился ли интерфейс, application binary interface, ABI если быть точным, или только реализация, и в зависимости от этого перекомпилировать только нужные файлы. Кроме того в Gradle с появлением Java Library plugin появилась возможность разделять compile зависимости на api и implementation. Любые изменения в implementation зависимости не приводят к повторной компиляции использующих ее модулей.
Вывод: ребята из Gradle сильно заморочились скоростью сборки. Результаты можно увидеть тут: https://blog.gradle.org/incremental-compiler-avoidance. А ведь ещё есть Gradle Cache, от котором расскажу отдельно.
P.S. для Maven есть сторонний плагин, заменяющий 5 стандартных и предоставляющий настоящую инкрементальную сборку http://takari.io/book/40-lifecycle.html Честно скажу, не тестировал, смущает, что решение внешнее #buildtool #java #ci
Всем привет!
Хочу рассказать собрать в одном посте несколько мало и среднеизвестных фичей Maven.
Поехали!
Для начала немного теории. Жизненный цикл сборки = фиксированная последовательность фаз сборки. К фазе сборки можно подключить плагин(ы), а точнее goal плагина. goal плагина - это некое действие, которое реализовано в плагине. Например у плагина compile есть goal compile. БольшАя часть goal стандартных плагинов уже подключена к нужным фазам сборки, но есть возможность переконфигурации в pom файле. https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
Общего механизма исключения той или иной фазы нет, исключение должен реализовать конкретный плагин.
1) Пропуск тестов и их компиляции: mvn install -Dmaven.test.skip=true. В отличие от mvn install -DskipTests также пропускает компиляцию тестов. Может быть полезно, если тесты вдруг сломались, и надо быстро сделать сборку. https://maven.apache.org/surefire/maven-surefire-plugin/examples/skipping-tests.html На всякий случай уточню - сломавшиеся тесты нужно чинить, откладывать исправление в долгий ящик - не нужно!)
2) Пропуск компиляции. Придумать случай, когда это может быть полезно, сложно. Если нужно выполнить какой-то один шаг сборки, без компиляции, то для этого есть более простой вариант, см. ниже. Но знать о возможности будет полезно: mvn package -Dmaven.main.skip
https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#skipMain
3) Запуск отдельной goal. Наряду с запуском цикла сборки можно запустить отдельную фазу. Например: mvn clean package sonar:sonar вначале выполняет два цикла сборки clean и package, а потом одну goal - sonar:sonar. В качестве альтернативы goal можно привязать к конкретной фазы. Кстати, порядок выполнения совпадает с порядком в коммандной строке.
https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html
4) Запуск падающего теста несколько раз, полезно в момент отладки, если тест "мигающий": mvn -Dsurefire.rerunFailingTestsCount=2 test
https://maven.apache.org/surefire/maven-surefire-plugin/examples/rerun-failing-tests.html
5) Среди стандартных плагинов Maven есть с виду неприметный help плагин. https://maven.apache.org/plugins/maven-help-plugin/
Среди его goal есть такие: mvn help:system - выдает информацию о системе: версию и производителя JDK, локаль, таймзоны, переменные среды.
mvn help:all-profiles - список активных профилей проекта.
На сегодня все. To be continued... #buildtool
Хочу рассказать собрать в одном посте несколько мало и среднеизвестных фичей Maven.
Поехали!
Для начала немного теории. Жизненный цикл сборки = фиксированная последовательность фаз сборки. К фазе сборки можно подключить плагин(ы), а точнее goal плагина. goal плагина - это некое действие, которое реализовано в плагине. Например у плагина compile есть goal compile. БольшАя часть goal стандартных плагинов уже подключена к нужным фазам сборки, но есть возможность переконфигурации в pom файле. https://maven.apache.org/guides/introduction/introduction-to-the-lifecycle.html
Общего механизма исключения той или иной фазы нет, исключение должен реализовать конкретный плагин.
1) Пропуск тестов и их компиляции: mvn install -Dmaven.test.skip=true. В отличие от mvn install -DskipTests также пропускает компиляцию тестов. Может быть полезно, если тесты вдруг сломались, и надо быстро сделать сборку. https://maven.apache.org/surefire/maven-surefire-plugin/examples/skipping-tests.html На всякий случай уточню - сломавшиеся тесты нужно чинить, откладывать исправление в долгий ящик - не нужно!)
2) Пропуск компиляции. Придумать случай, когда это может быть полезно, сложно. Если нужно выполнить какой-то один шаг сборки, без компиляции, то для этого есть более простой вариант, см. ниже. Но знать о возможности будет полезно: mvn package -Dmaven.main.skip
https://maven.apache.org/plugins/maven-compiler-plugin/compile-mojo.html#skipMain
3) Запуск отдельной goal. Наряду с запуском цикла сборки можно запустить отдельную фазу. Например: mvn clean package sonar:sonar вначале выполняет два цикла сборки clean и package, а потом одну goal - sonar:sonar. В качестве альтернативы goal можно привязать к конкретной фазы. Кстати, порядок выполнения совпадает с порядком в коммандной строке.
https://maven.apache.org/guides/getting-started/maven-in-five-minutes.html
4) Запуск падающего теста несколько раз, полезно в момент отладки, если тест "мигающий": mvn -Dsurefire.rerunFailingTestsCount=2 test
https://maven.apache.org/surefire/maven-surefire-plugin/examples/rerun-failing-tests.html
5) Среди стандартных плагинов Maven есть с виду неприметный help плагин. https://maven.apache.org/plugins/maven-help-plugin/
Среди его goal есть такие: mvn help:system - выдает информацию о системе: версию и производителя JDK, локаль, таймзоны, переменные среды.
mvn help:all-profiles - список активных профилей проекта.
На сегодня все. To be continued... #buildtool
maven.apache.org
Introduction to the Build Lifecycle – Maven
Всем привет!
Про фишки Maven, часть вторая.
Поехали.
1) У maven, также как у Gradle, есть wrapper. Что это такое? Это по сути встроенный в проект maven. А точнее в проекте появляется .mvn\wrapper\maven-wrapper.jar, а также скачивается дистрибутив maven в папку локального репозитория .m2. Позволяет не зависеть от наличия и версии maven в системе.
Установка: mvn wrapper:wrapper
Использование: ./mvnw clean package вместо mvn clean package
Добавленные при установке wrapper в проект файлы нужно закоммитить в git.
https://maven.apache.org/wrapper/
2) Maven daemon. Тоже перетекание хороших идей из Gradle. Как следует из названия это Maven, который запускается в фоновом режиме. Что позволяет убрать фазу инициализации maven при втором и последующих запусках и существенно ускорить сборку на маленьких проектах. К сожалению, это сторонний проект, поэтому не совместим с maven wrapper.
https://github.com/apache/maven-mvnd
3) Maven умеет запускать сборку в несколько потоков. Рекомендуется сделать число потоков равное числу ядер процессора:
mvn -T 1C clean package
Но можно указать и конкретное число потоков:
mvn -T 4 clean package
И даже число потоков на ядро:
mvn -T 1.5C clean package
Список плагинов, которые могут работать параллельно и другую информацию можно найти здесь:
https://cwiki.apache.org/confluence/display/MAVEN/Parallel+builds+in+Maven+3
Также эта настройка есть в IntelliJ IDEA.
Про настройки параллельного запуска для JUnit тестов можно почитать тут: https://www.baeldung.com/maven-junit-parallel-tests
4) Плагин Maven Enforсer. Позволяет при сборке на фазе validate проверять:
а) версию и вендора JDK
б) версию maven
в) отсутствие SNAPSHOT версий
г) наличие и отсутствие в проекте определенных файлов и их контрольную сумму
д) отсутствие repositories в pom файлах (они должны быть в settings.xml)
е) ОС, на которой запущен maven
и многое другое.
https://maven.apache.org/enforcer/enforcer-rules/index.html
5) BuildPlan плагин. Показывает какие плагины используются при сборке, какие goal связаны с каждой фазой. Запускать нужно с теми же ключами -D и -P,
с которыми проводится сборка:
mvn buildplan:list
http://buildplan.jcgay.fr/
#buildtool
Про фишки Maven, часть вторая.
Поехали.
1) У maven, также как у Gradle, есть wrapper. Что это такое? Это по сути встроенный в проект maven. А точнее в проекте появляется .mvn\wrapper\maven-wrapper.jar, а также скачивается дистрибутив maven в папку локального репозитория .m2. Позволяет не зависеть от наличия и версии maven в системе.
Установка: mvn wrapper:wrapper
Использование: ./mvnw clean package вместо mvn clean package
Добавленные при установке wrapper в проект файлы нужно закоммитить в git.
https://maven.apache.org/wrapper/
2) Maven daemon. Тоже перетекание хороших идей из Gradle. Как следует из названия это Maven, который запускается в фоновом режиме. Что позволяет убрать фазу инициализации maven при втором и последующих запусках и существенно ускорить сборку на маленьких проектах. К сожалению, это сторонний проект, поэтому не совместим с maven wrapper.
https://github.com/apache/maven-mvnd
3) Maven умеет запускать сборку в несколько потоков. Рекомендуется сделать число потоков равное числу ядер процессора:
mvn -T 1C clean package
Но можно указать и конкретное число потоков:
mvn -T 4 clean package
И даже число потоков на ядро:
mvn -T 1.5C clean package
Список плагинов, которые могут работать параллельно и другую информацию можно найти здесь:
https://cwiki.apache.org/confluence/display/MAVEN/Parallel+builds+in+Maven+3
Также эта настройка есть в IntelliJ IDEA.
Про настройки параллельного запуска для JUnit тестов можно почитать тут: https://www.baeldung.com/maven-junit-parallel-tests
4) Плагин Maven Enforсer. Позволяет при сборке на фазе validate проверять:
а) версию и вендора JDK
б) версию maven
в) отсутствие SNAPSHOT версий
г) наличие и отсутствие в проекте определенных файлов и их контрольную сумму
д) отсутствие repositories в pom файлах (они должны быть в settings.xml)
е) ОС, на которой запущен maven
и многое другое.
https://maven.apache.org/enforcer/enforcer-rules/index.html
5) BuildPlan плагин. Показывает какие плагины используются при сборке, какие goal связаны с каждой фазой. Запускать нужно с теми же ключами -D и -P,
с которыми проводится сборка:
mvn buildplan:list
http://buildplan.jcgay.fr/
#buildtool
GitHub
GitHub - apache/maven-mvnd: Apache Maven Daemon
Apache Maven Daemon. Contribute to apache/maven-mvnd development by creating an account on GitHub.
Всем привет!
Хочу рассказать про ряд неочевидных особенностей enum в Java.
Поехали!
1) enum - это полноценный класс, у него могут быть поля, методы, обычные и статические
2) любой enum неявно (!) расширяет абстрактный класс Enum, поэтому наследовать enum от кого-то другого нельзя. Например, один enum от другого. При этом добавить final для enum нельзя, т.к. он уже фактически final)
3) зато enum может реализовывать интерфейсы, как и любой другой класс в Java
4) самое интересное - каждое значение enum является наследником самого enum. Т.е. можно объявить метод show в enum Direction и переопределить его Direction.LEFT. На первый взгляд противоречит пункту 2, но см. дальше
5) Как устроен enum внутри?
Возьмем такой enum:
enum Direction {
LEFT {
@Override
void show() {}
},
RIGTH;
void show() {}
}
Если посмотреть его байткод, упросить его и мысленно декомпилировать обратно в Java, то получится:
public class Direction extends Enum {
public static final Direction LEFT = new Direction() {
@Override
void show() {
}
};
public static final Direction RIGTH = new Direction();
private static final Direction[] VALUES = values();
private Direction(String name, int ordinal) {
super(name, ordinal);
}
public static Direction[] values() {
return VALUES.clone();
}
public static Direction valueOf(String name) {
return Enum.valueOf(Direction.class, name);
}
void show() {
}
}
Т.е. enum превращается в класс со статическими public полями. И у класса создаются анонимные классы-наследники.
Собственно, если бы в Java не было enum - я бы реализовал перечисление примерно также
6) enum не может иметь public конструктор, и метод clone для него возвращает CloneNotSupportedException. И как я говорил выше он фактически финальный. Следовательно, во время работы программы число объектов enum равно число значений enum
7) из-за этого с помощью enum легко можно реализовать singleton. Можно, но не нужно, если вы используете Spring или другой IoC контейнер) Но об этом как-нибудь позже
8) исходя из сказанного выше enum-ы можно и нужно сравнивать по ==. Во-первых это нагляднее, а во-вторых меньше шансов получить NPE неправильно написав код сравнения.
9) хочу обратить внимание на классы EnumSet и EnumMap, которые эффективно с точки зрения расхода памяти позволяют использовать значения Enum в качестве ключей. Enum реализует Comparable, сравнение идет по полю ordinal - порядковому номеру значения.
10) Enum реализует Serializible и успешно сериализуется. Но есть одна важная особенность. При сериализации конкретного объекта сохраняется лишь его имя, и объект восстанавливается потом вызовом valueOf. Значения других полей теряются. Следовательно, enum должен быть реализован иммутабельным, значения всех полей должны задаваться в конструкторе.
#java
Хочу рассказать про ряд неочевидных особенностей enum в Java.
Поехали!
1) enum - это полноценный класс, у него могут быть поля, методы, обычные и статические
2) любой enum неявно (!) расширяет абстрактный класс Enum, поэтому наследовать enum от кого-то другого нельзя. Например, один enum от другого. При этом добавить final для enum нельзя, т.к. он уже фактически final)
3) зато enum может реализовывать интерфейсы, как и любой другой класс в Java
4) самое интересное - каждое значение enum является наследником самого enum. Т.е. можно объявить метод show в enum Direction и переопределить его Direction.LEFT. На первый взгляд противоречит пункту 2, но см. дальше
5) Как устроен enum внутри?
Возьмем такой enum:
enum Direction {
LEFT {
@Override
void show() {}
},
RIGTH;
void show() {}
}
Если посмотреть его байткод, упросить его и мысленно декомпилировать обратно в Java, то получится:
public class Direction extends Enum {
public static final Direction LEFT = new Direction() {
@Override
void show() {
}
};
public static final Direction RIGTH = new Direction();
private static final Direction[] VALUES = values();
private Direction(String name, int ordinal) {
super(name, ordinal);
}
public static Direction[] values() {
return VALUES.clone();
}
public static Direction valueOf(String name) {
return Enum.valueOf(Direction.class, name);
}
void show() {
}
}
Т.е. enum превращается в класс со статическими public полями. И у класса создаются анонимные классы-наследники.
Собственно, если бы в Java не было enum - я бы реализовал перечисление примерно также
6) enum не может иметь public конструктор, и метод clone для него возвращает CloneNotSupportedException. И как я говорил выше он фактически финальный. Следовательно, во время работы программы число объектов enum равно число значений enum
7) из-за этого с помощью enum легко можно реализовать singleton. Можно, но не нужно, если вы используете Spring или другой IoC контейнер) Но об этом как-нибудь позже
8) исходя из сказанного выше enum-ы можно и нужно сравнивать по ==. Во-первых это нагляднее, а во-вторых меньше шансов получить NPE неправильно написав код сравнения.
9) хочу обратить внимание на классы EnumSet и EnumMap, которые эффективно с точки зрения расхода памяти позволяют использовать значения Enum в качестве ключей. Enum реализует Comparable, сравнение идет по полю ordinal - порядковому номеру значения.
10) Enum реализует Serializible и успешно сериализуется. Но есть одна важная особенность. При сериализации конкретного объекта сохраняется лишь его имя, и объект восстанавливается потом вызовом valueOf. Значения других полей теряются. Следовательно, enum должен быть реализован иммутабельным, значения всех полей должны задаваться в конструкторе.
#java
Всем привет! Есть такое правило - в логах в ПРОДе не должно быть ничего лишнего. Т.е. с одной стороны логи нужны для хранения стек-трейса исключения и другой полезной при разборе ошибки информации. Есть много инструментов, позволяющих строить по логам метрики, их парсить по заранее настроенной маске, вытаскивая полезную информацию. А с другой стороны логирование, как и любой другой функционал, жрет ресурсы. Как много? Вот так. На картинке показано число обработанных запросов для простейшего контроллера с логированием и без. Причём логирование происходило в RAM диск, т.е не учитываем задержки файловой системы. И шаблон сообщения в лог простой, контекст логирования для тех фреймворков, которые позволяют его настраивать, не используется. Вывод: отказываться от логирования не стоит, асинхрон рулит, а главное про уровни логирования и их настройку на ПРОДе забывать не стоит. Подробнее про условия теста: https://blog.sebastian-daschner.com/entries/logging-performance-comparison #java #logging #performance
Всем привет!
Сегодня поговорим про память в JVM.
Все знают про heap и stack. Начнем с них.
1) heap, она же куча. Хранит все создаваемые при работе JVM объекты. Очищается сборщиком мусора - garbage collector.
Делится на поколения: Eden, Survivor и Tenured (Old), между которыми перемещаются выжившие при сборке мусора объекты.
Кроме объектов начиная с Java 7 хранит также пул строк и другие пулы. Другие пулы - это пулы базовых типов-обверток над примитивами с фиксированным числом значений. Т.е. Boolean, Byte, Short, Integer, Long, Character. Цель использования пулов - уменьшение объема используемой памяти.
Раз пулы находятся в куче, то можно предположить, что неиспользуемые значения также удаляются сборщиком мусора. Так и есть.
Размер кучи можно регулировать параметрами: java -Xms256m -Xmx2048m
-Xms - начальный размер
-Xmx - максимальный размер.
По умолчанию максимальный размер для 64 разрядной системы при работе в сервером режиме равен 1/4 оперативной памяти, но не более 32 Гб.
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html#default_heap_size
Посмотреть размер памяти у запущенного процесса: java -XX:+PrintFlagsFinal <GC options> -version | grep MaxHeapSize.
Размер пулов тоже настраивается, пример для пула строк: -XX:StringTableSize=4901
Если памяти не хватило - вылетает OutOfMemoryError: Java heap space
2) stack. Хранит стек вызовов всех Java потоков. В стеке хранятся локальные переменные и параметры примитивных типов, а также ссылки на объекты локальных переменных и параметров. Сами объекты при этом хранятся в куче.
Размер регулируется параметром -Xss. Как правило занимает немного, хотя в теории 100 потоков - это 100 Мб. Не стоит забывать, что кроме созданных в коде потоков есть служебные - потоки сборщика мусора и потоки JIT компилятора. Если памяти не хватило, а это как правило означает бесконечную рекурсию, то вылетает https://stackoverflow.com/questions/214741/what-is-a-stackoverflowerror
Также некоторые могут вспомнить про MetaSpace)
3) MetaSpace, он же Class metadata. Хранит метаданные классов, загружаемые classloaders. А классов в Java много - стандартная библиотека rt.jar, Spring, другие используемые библиотеки, ваши собственные классы, генерация классов при работе программы.
До Java 8 классы хранились в области с названием PermGenSpace. Возможно кто-то еще помнит, а может и сталкивается до сих пор с OutOfMemoryError: PermGen space. (((
Отличия MetaSpace от PermGenSpace - неограниченный размер по умолчанию и то, что с ним наконец-то научился работать сборщик мусора. Хотя размер по умолчанию не ограничен, регулировать его при желании можно: XX:MaxMetaspaceSize и XX:CompressedClassSpaceMax. При желании можно получить и OutOfMemoryError: Metaspace)))
Оффтоп: почему опции две? В зависимости от включения UseCompressedOops и UseCompressedClassesPointers класс попадает либо в обычный MetaSpace, либо в MetaSpace для классов с сжатыми указателями. MaxMetaspaceSize включает в себя CompressedClassSpaceMax.
Про сжатые указатели https://habr.com/ru/post/440166/
Теперь пойдут области, покрытые мраком)
4) GC - область памяти, используемая Garbage Collector при работе. Занимает 3-4% от кучи. Также размер зависит от типа сборщика мусора.
5) JIT\Code - как известно, компилятор у Java довольно тупой, а вот JVM - умная) При работе она анализирует использование классов, выполняет разные оптимизации, а также компилирует часто используемые классы в нативный код. Процесс называется Just In Time Compilation. Именно в этой области скомпилированный код и хранится.
Размер регулируется ключом: XX:ReservedCodeCacheSize. Если установить слишком маленький размер - будет постоянная рекомпиляция, начнет страдать производительность. Больше опций, регулирующих процесс тут - https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm
6) JIT\Compiler - собственно память, занимаемая компилятором.
Сегодня поговорим про память в JVM.
Все знают про heap и stack. Начнем с них.
1) heap, она же куча. Хранит все создаваемые при работе JVM объекты. Очищается сборщиком мусора - garbage collector.
Делится на поколения: Eden, Survivor и Tenured (Old), между которыми перемещаются выжившие при сборке мусора объекты.
Кроме объектов начиная с Java 7 хранит также пул строк и другие пулы. Другие пулы - это пулы базовых типов-обверток над примитивами с фиксированным числом значений. Т.е. Boolean, Byte, Short, Integer, Long, Character. Цель использования пулов - уменьшение объема используемой памяти.
Раз пулы находятся в куче, то можно предположить, что неиспользуемые значения также удаляются сборщиком мусора. Так и есть.
Размер кучи можно регулировать параметрами: java -Xms256m -Xmx2048m
-Xms - начальный размер
-Xmx - максимальный размер.
По умолчанию максимальный размер для 64 разрядной системы при работе в сервером режиме равен 1/4 оперативной памяти, но не более 32 Гб.
https://docs.oracle.com/javase/8/docs/technotes/guides/vm/gctuning/parallel.html#default_heap_size
Посмотреть размер памяти у запущенного процесса: java -XX:+PrintFlagsFinal <GC options> -version | grep MaxHeapSize.
Размер пулов тоже настраивается, пример для пула строк: -XX:StringTableSize=4901
Если памяти не хватило - вылетает OutOfMemoryError: Java heap space
2) stack. Хранит стек вызовов всех Java потоков. В стеке хранятся локальные переменные и параметры примитивных типов, а также ссылки на объекты локальных переменных и параметров. Сами объекты при этом хранятся в куче.
Размер регулируется параметром -Xss. Как правило занимает немного, хотя в теории 100 потоков - это 100 Мб. Не стоит забывать, что кроме созданных в коде потоков есть служебные - потоки сборщика мусора и потоки JIT компилятора. Если памяти не хватило, а это как правило означает бесконечную рекурсию, то вылетает https://stackoverflow.com/questions/214741/what-is-a-stackoverflowerror
Также некоторые могут вспомнить про MetaSpace)
3) MetaSpace, он же Class metadata. Хранит метаданные классов, загружаемые classloaders. А классов в Java много - стандартная библиотека rt.jar, Spring, другие используемые библиотеки, ваши собственные классы, генерация классов при работе программы.
До Java 8 классы хранились в области с названием PermGenSpace. Возможно кто-то еще помнит, а может и сталкивается до сих пор с OutOfMemoryError: PermGen space. (((
Отличия MetaSpace от PermGenSpace - неограниченный размер по умолчанию и то, что с ним наконец-то научился работать сборщик мусора. Хотя размер по умолчанию не ограничен, регулировать его при желании можно: XX:MaxMetaspaceSize и XX:CompressedClassSpaceMax. При желании можно получить и OutOfMemoryError: Metaspace)))
Оффтоп: почему опции две? В зависимости от включения UseCompressedOops и UseCompressedClassesPointers класс попадает либо в обычный MetaSpace, либо в MetaSpace для классов с сжатыми указателями. MaxMetaspaceSize включает в себя CompressedClassSpaceMax.
Про сжатые указатели https://habr.com/ru/post/440166/
Теперь пойдут области, покрытые мраком)
4) GC - область памяти, используемая Garbage Collector при работе. Занимает 3-4% от кучи. Также размер зависит от типа сборщика мусора.
5) JIT\Code - как известно, компилятор у Java довольно тупой, а вот JVM - умная) При работе она анализирует использование классов, выполняет разные оптимизации, а также компилирует часто используемые классы в нативный код. Процесс называется Just In Time Compilation. Именно в этой области скомпилированный код и хранится.
Размер регулируется ключом: XX:ReservedCodeCacheSize. Если установить слишком маленький размер - будет постоянная рекомпиляция, начнет страдать производительность. Больше опций, регулирующих процесс тут - https://docs.oracle.com/javase/8/embedded/develop-apps-platforms/codecache.htm
6) JIT\Compiler - собственно память, занимаемая компилятором.
Stack Overflow
What is a StackOverflowError?
What is a StackOverflowError, what causes it, and how should I deal with them?
Ну и самые кишочки... )))
7) Internal - память под внутренние нужны JVM, не упомянутые выше.
В частности здесь выделяются Direct ByteBuffers - https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html В случае объекта в heap, содержащего данные файла, ОС вначале копирует данные в свой низкоуровневый буфер, а потом JVM копирует данные к себе, то в случае Direct ByteBuffers этого можно избежать. Используются во многих высоконагруженных системах, в частности Kafka.
Размер настраивается через XX:MaxDirectMemorySize=N. Нехватку памяти можно определить по "OutOfMemoryError: Direct buffer memory".
Т.к. ByteBuffer - объект, то сборщик мусора также умеет убирать и данные, выделенные в Direct ByteBuffers. Правда с некоторой задержкой, т.к. сами данные все же находятся не в куче, и механизм уборки получается чуть более сложный.
Сравнение скорости работы прямого и heap буфера с комментарием о том, что это всего лишь один из возможных случаев использования: https://elizarov.livejournal.com/20381.html
А увидеть все эти области можно запустив java процесс со специальным ключом XX:NativeMemoryTracking и используя утилиту jcmd из состава JDK.
Cм. детали тут https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html
Запускать в ПРОДе с этой опцией нужно с осторожностью, overhead может быть 5-10%
Может показаться, что главное - это heap, все остальное по сравнению с ним - мелочи. Это не всегда так, см. исследование https://shipilev.net/jvm/anatomy-quarks/
#java #interview_question
7) Internal - память под внутренние нужны JVM, не упомянутые выше.
В частности здесь выделяются Direct ByteBuffers - https://docs.oracle.com/javase/8/docs/api/java/nio/ByteBuffer.html В случае объекта в heap, содержащего данные файла, ОС вначале копирует данные в свой низкоуровневый буфер, а потом JVM копирует данные к себе, то в случае Direct ByteBuffers этого можно избежать. Используются во многих высоконагруженных системах, в частности Kafka.
Размер настраивается через XX:MaxDirectMemorySize=N. Нехватку памяти можно определить по "OutOfMemoryError: Direct buffer memory".
Т.к. ByteBuffer - объект, то сборщик мусора также умеет убирать и данные, выделенные в Direct ByteBuffers. Правда с некоторой задержкой, т.к. сами данные все же находятся не в куче, и механизм уборки получается чуть более сложный.
Сравнение скорости работы прямого и heap буфера с комментарием о том, что это всего лишь один из возможных случаев использования: https://elizarov.livejournal.com/20381.html
А увидеть все эти области можно запустив java процесс со специальным ключом XX:NativeMemoryTracking и используя утилиту jcmd из состава JDK.
Cм. детали тут https://docs.oracle.com/javase/8/docs/technotes/guides/troubleshoot/tooldescr007.html
Запускать в ПРОДе с этой опцией нужно с осторожностью, overhead может быть 5-10%
Может показаться, что главное - это heap, все остальное по сравнению с ним - мелочи. Это не всегда так, см. исследование https://shipilev.net/jvm/anatomy-quarks/
#java #interview_question
Livejournal
Детали реализации имеют значение или direct ByteBuffer vs heap ByteBuffer
При написании кода для максимальной производительности особое внимание необходимо уделять используемым библиотекам, даже если эти библиотеки являются частью вашего языка программирования. Необходимо подробно изучать не только контракты, описанные в их API…
Для иллюстрации предыдущего поста. Вот как разместиться в памяти такой код:
class MainClass {
void method1() { //<- main
int variable1 = 1;
Class1 variable2 = new Class1();
variable2.method2();
}
}
class Class1 {
static Class2 classVariable4 = new Class2();
int instanceVariable5 = 0;
Class2 instanceVariable6 = new Class2();
void method2() {
int variable3 = 3;
}
}
class Class2 { }
Взял отсюда https://stackoverflow.com/questions/362740/java-memory-model-can-someone-explain-it/362804
class MainClass {
void method1() { //<- main
int variable1 = 1;
Class1 variable2 = new Class1();
variable2.method2();
}
}
class Class1 {
static Class2 classVariable4 = new Class2();
int instanceVariable5 = 0;
Class2 instanceVariable6 = new Class2();
void method2() {
int variable3 = 3;
}
}
class Class2 { }
Взял отсюда https://stackoverflow.com/questions/362740/java-memory-model-can-someone-explain-it/362804
Всем привет!
Что входит в состав JDK - Java Development Kit - кроме стандартной библиотеки Java?
Разные утилиты, сразу можно вспомнить java и javaс. Но не только они, есть еще ряд полезных консольных утилит, о которых расскажу ниже.
Сразу скажу, про таких монстров, как JVisualVM и Mission Control, стоит рассказать отдельно, что я и сделаю. Но потом)
Все утилиты лежат в папке bin внутри JDK.
Поехали:
1) javap - прочитать версию байткода, а также увидеть байткод в человекочитаемом виде.
Как запускать: javap -verbose Class1.class
2) jcmd - позволяет отправлять команды работающему процессу. Что за команды - да самые разные, например:
- сделать дамп кучи
- показать статистику использования памяти, причем после некоторых манипуляция - любых областей памяти, не только кучи
- время работы и другую информацию о JVM
- информацию о запущенных процессах
Если запусить jcmd без параметров - покажет список работающих java процессов.
Если запустить ее как jcmd PID - покажет доступные для процесса команды.
Описание почитать можно тут https://docs.oracle.com/en/java/javase/17/troubleshoot/diagnostic-tools.html#GUID-42A18B29-B4AD-4831-B846-2CDBA55F2254
3) jstat - показывает краткую статистику по Java процессу.
jstat -class PID - иннформация по загрузке классов
jstat -compiler PID - по JIT компиляции
jstat -gc PID - информация по работе GC, ее расшифровку и другие опции см. тут https://docs.oracle.com/en/java/javase/11/tools/jstat.html#GUID-5F72A7F9-5D5A-4486-8201-E1D1BA8ACCB5
Аналог линуксовых vmstat, iostat.
4) keytool - утилита для работы с сертификатами в формате jks.
5) jshell - начиная с Java 9 появляется консоль для выполнения Java кода интерактивно, без создания классов.
Что удобно: не нужно создавать класс, import-ы, ставить ; в конце строки, просто можно проверить выполнение какого-то простого кода.
6) jdeprscan - сканирует jar, класс или папку на наличие Deprecated API. Можно использовать в DevOps.
7) jconsole - графическая утилита для отображения использования CPU, памяти и другой информации, которую может отображать jcmd. Также показывает MBean-ы. Главное ее достоинство заключается в том, что она полностью бесплатна и есть в любой JDK.
При этом JVisualVM и Mission Control могут гораздо больше, но с ними есть нюансы)
Полный список утилит можно найти здесь: https://docs.oracle.com/en/java/javase/11/tools/tools-and-command-reference.html
#jdk #java #tools
Что входит в состав JDK - Java Development Kit - кроме стандартной библиотеки Java?
Разные утилиты, сразу можно вспомнить java и javaс. Но не только они, есть еще ряд полезных консольных утилит, о которых расскажу ниже.
Сразу скажу, про таких монстров, как JVisualVM и Mission Control, стоит рассказать отдельно, что я и сделаю. Но потом)
Все утилиты лежат в папке bin внутри JDK.
Поехали:
1) javap - прочитать версию байткода, а также увидеть байткод в человекочитаемом виде.
Как запускать: javap -verbose Class1.class
2) jcmd - позволяет отправлять команды работающему процессу. Что за команды - да самые разные, например:
- сделать дамп кучи
- показать статистику использования памяти, причем после некоторых манипуляция - любых областей памяти, не только кучи
- время работы и другую информацию о JVM
- информацию о запущенных процессах
Если запусить jcmd без параметров - покажет список работающих java процессов.
Если запустить ее как jcmd PID - покажет доступные для процесса команды.
Описание почитать можно тут https://docs.oracle.com/en/java/javase/17/troubleshoot/diagnostic-tools.html#GUID-42A18B29-B4AD-4831-B846-2CDBA55F2254
3) jstat - показывает краткую статистику по Java процессу.
jstat -class PID - иннформация по загрузке классов
jstat -compiler PID - по JIT компиляции
jstat -gc PID - информация по работе GC, ее расшифровку и другие опции см. тут https://docs.oracle.com/en/java/javase/11/tools/jstat.html#GUID-5F72A7F9-5D5A-4486-8201-E1D1BA8ACCB5
Аналог линуксовых vmstat, iostat.
4) keytool - утилита для работы с сертификатами в формате jks.
5) jshell - начиная с Java 9 появляется консоль для выполнения Java кода интерактивно, без создания классов.
Что удобно: не нужно создавать класс, import-ы, ставить ; в конце строки, просто можно проверить выполнение какого-то простого кода.
6) jdeprscan - сканирует jar, класс или папку на наличие Deprecated API. Можно использовать в DevOps.
7) jconsole - графическая утилита для отображения использования CPU, памяти и другой информации, которую может отображать jcmd. Также показывает MBean-ы. Главное ее достоинство заключается в том, что она полностью бесплатна и есть в любой JDK.
При этом JVisualVM и Mission Control могут гораздо больше, но с ними есть нюансы)
Полный список утилит можно найти здесь: https://docs.oracle.com/en/java/javase/11/tools/tools-and-command-reference.html
#jdk #java #tools
Oracle Help Center
Troubleshooting Guide
The Java Development Kit (JDK) provides diagnostic tools and troubleshooting tools specific to various operating systems. Custom diagnostic tools can also be developed using the APIs provided by the JDK.
Всем привет!
Я вернулся из отпуска, значит настало время для нового поста.
Сегодня хотел бы рассказать о об одном своем провальном проекте. Ведь далеко не все проекты попадают в ПРОМ.
И это нормально, главное - делать выводы из ошибок.
Поехали.
Задача стояла такая. Есть две БД с одинаковой схемой - структурой таблиц.
Данные в БД - пользователь + связанная с ним информация. А это еще порядка сотни таблиц, причем их число растет с каждым релизом.
Требуется периодически переносить часть пользователей из одной БД в другую. Назовем утилиту, которая будет это делать - мигратор.
Навскидку видится три варианта:
1) ETL
2) самописный скрипт БД
3) Java мигратор, работающий на основании метаданных из Hibernate. Да, забыл уточнить, есть Java приложение, работающее со всеми таблицами пользователя через Hibernate.
Наша команда занималась третьим вариантом. Какие я сейчас вижу проблемы у этого варианта:
1) Java мигратор самый сложный и непрозрачный из всех вариантов. Главный его плюс - он практически убирает необходимость ручной доработки мигратора с выходом новых релизов. И убирает двойную работу - другие варианты требуют сначала обновления скриптов версионирования БД, а потом обновления мигратора. Где может выстрелить сложность Java мигратора? БД сопровождают специально обученные люди - DBA. Они достаточно консервативны, т.к. во-первых они сопровождение, а во-вторых - DBA) В нашем случае на ПРОМ скрипты накатывались вручную, хотя разработка предоставляла Liquibase скрипты. Изначально со стороны DBA было заметно недоверие к Java мигратору. Чтобы его снизить решили, что мигратор будет реконструировать схему БД, создавать список таблиц, связанных с пользователем, а DBA всегда будут иметь возможность отфильтровать этот список. Т.е. миграция данных идет по "белому списку". Сопровождение предварительно одобрило такой. При этом активно в процессе выставления требований и тестирования не участвовало.
2) отсутствие внятной процедуры приемки скриптов. Снизить непрозрачность мигратора можно с помощью создания набора тест-кейсов со 100% покрытием на ПРОМ like обезличенных данных. С согласованием процедуры у DBA. Мы этого не сделали. Т.е. тестирование конечно было, но оно проводилось на одном тестовом клиенте, нормального плана не было, консультации с DBA были фрагментарными.
3) растянутость разработки во времени из-за недостатка времени. Задача была экспериментальной, разработка шла больше года, с переодическим переключением на другие задачи. Это привело к падению качества кода. Ни я как тимлид, ни разработчики не дошли до нормального рефакторинга. Модульных тестов было мало. Плюс использовалась рефлексия, что еще больше усложнило код. Когда к моменту принятия решения о внедрении или не внедрении Java мигратора когда я смотрел на код - мне было страшновато(((
4) задача не была вовремя закрыта. На задачу спалили больше человекогода. В результате на ПРОМ использовался самописный SQL мигратор. Сейчас я бы попробовал получить от заказчика четкое ОК на внедрение, создав совместно с ним план тестирования и приемки. Agile и все такое) А если бы этого ОК не было - прекратил бы разработку.
#projects #JPA #fuckup
Я вернулся из отпуска, значит настало время для нового поста.
Сегодня хотел бы рассказать о об одном своем провальном проекте. Ведь далеко не все проекты попадают в ПРОМ.
И это нормально, главное - делать выводы из ошибок.
Поехали.
Задача стояла такая. Есть две БД с одинаковой схемой - структурой таблиц.
Данные в БД - пользователь + связанная с ним информация. А это еще порядка сотни таблиц, причем их число растет с каждым релизом.
Требуется периодически переносить часть пользователей из одной БД в другую. Назовем утилиту, которая будет это делать - мигратор.
Навскидку видится три варианта:
1) ETL
2) самописный скрипт БД
3) Java мигратор, работающий на основании метаданных из Hibernate. Да, забыл уточнить, есть Java приложение, работающее со всеми таблицами пользователя через Hibernate.
Наша команда занималась третьим вариантом. Какие я сейчас вижу проблемы у этого варианта:
1) Java мигратор самый сложный и непрозрачный из всех вариантов. Главный его плюс - он практически убирает необходимость ручной доработки мигратора с выходом новых релизов. И убирает двойную работу - другие варианты требуют сначала обновления скриптов версионирования БД, а потом обновления мигратора. Где может выстрелить сложность Java мигратора? БД сопровождают специально обученные люди - DBA. Они достаточно консервативны, т.к. во-первых они сопровождение, а во-вторых - DBA) В нашем случае на ПРОМ скрипты накатывались вручную, хотя разработка предоставляла Liquibase скрипты. Изначально со стороны DBA было заметно недоверие к Java мигратору. Чтобы его снизить решили, что мигратор будет реконструировать схему БД, создавать список таблиц, связанных с пользователем, а DBA всегда будут иметь возможность отфильтровать этот список. Т.е. миграция данных идет по "белому списку". Сопровождение предварительно одобрило такой. При этом активно в процессе выставления требований и тестирования не участвовало.
2) отсутствие внятной процедуры приемки скриптов. Снизить непрозрачность мигратора можно с помощью создания набора тест-кейсов со 100% покрытием на ПРОМ like обезличенных данных. С согласованием процедуры у DBA. Мы этого не сделали. Т.е. тестирование конечно было, но оно проводилось на одном тестовом клиенте, нормального плана не было, консультации с DBA были фрагментарными.
3) растянутость разработки во времени из-за недостатка времени. Задача была экспериментальной, разработка шла больше года, с переодическим переключением на другие задачи. Это привело к падению качества кода. Ни я как тимлид, ни разработчики не дошли до нормального рефакторинга. Модульных тестов было мало. Плюс использовалась рефлексия, что еще больше усложнило код. Когда к моменту принятия решения о внедрении или не внедрении Java мигратора когда я смотрел на код - мне было страшновато(((
4) задача не была вовремя закрыта. На задачу спалили больше человекогода. В результате на ПРОМ использовался самописный SQL мигратор. Сейчас я бы попробовал получить от заказчика четкое ОК на внедрение, создав совместно с ним план тестирования и приемки. Agile и все такое) А если бы этого ОК не было - прекратил бы разработку.
#projects #JPA #fuckup