(java || kotlin) && devOps
369 subscribers
6 photos
1 video
6 files
306 links
Полезное про Java и Kotlin - фреймворки, паттерны, тесты, тонкости JVM. Немного архитектуры. И DevOps, куда без него
Download Telegram
Всем привет! Меня зовут Денис, можно просто Дэн. Я пришёл в разработку 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
(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
Всем привет! Решил написать пост про архитектурную проблему, с которой столкнулся недавно. Где делать сортировку данных? Обычно проблема звучит так: на клиенте, т.е в коде приложения, или на сервере, т.е. в БД? У нас все чуть сложнее. Дано:
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
Всем привет!
На данный момент для сборки 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, часть вторая.
Поехали.
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
Всем привет!

Хочу рассказать про ряд неочевидных особенностей 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 - собственно память, занимаемая компилятором.
Ну и самые кишочки... )))

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
Для иллюстрации предыдущего поста. Вот как разместиться в памяти такой код:

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