Виктор Яковлевич Пан (род. 8 сентября 1939, Москва) — советско-американский математик/информатик; алгоритмы для полиномов и быстрого матричного умножения.
Вайдьешваран Раджараман (родился в 1933 году) — пионер информатики в Индии; запуск первых академических программ по CS в IIT Канпур.
1636 — основан Гарвардский университет, старейший из университетов Нового Света.
#Biography #Birth_Date #Events #08Сентября
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Docker Compose и микросервисы
Основы: docker-compose.yaml и его архитектура
Что такое docker-compose.yaml?
Это декларативный конфигурационный файл, управляющий многоконтейнерными приложениями через Docker Compose. В отличие от ручного запуска контейнеров через docker run, Compose абстрагирует сложность оркестрации: один файл определяет сервисы, сети, тома и зависимости. При выполнении docker-compose up Docker Engine создает изолированную среду, где контейнеры взаимодействуют через виртуальные сети, а данные сохраняются в управляемых томах.
Как это работает на уровне ядра?
Когда вы запускаете docker-compose up, Docker Daemon:
1. Парсит YAML-файл и строит граф зависимостей сервисов.
2. Создает изолированные *сетевые пространства имен* (network namespaces) для каждого сервиса, если не указано иное.
3. Назначает виртуальные Ethernet-интерфейсы (veth pairs) для соединения контейнеров в bridge-сеть по умолчанию.
4. Для томов (volumes) монтирует директории хоста или управляемые Docker тома в контейнеры через bind mounts или overlayfs.
Структура docker-compose: services, volumes, networks
1. services
Определяет контейнеры как логические сервисы.
Пример:
- image — базовый образ (загружается из Docker Hub, если отсутствует локально).
- ports — проброс портов хоста в контейнер. Формат HOST:CONTAINER. При старте Docker создает iptables-правила для перенаправления трафика.
- Важно: Контейнеры в одной сети Compose общаются по имени сервиса (например, web может обращаться к db по DNS-имени db:5432). Это достигается через встроенный DNS-резолвер Docker, который динамически обновляет записи при старте/остановке сервисов.
2. volumes
Управляет данными, сохраняемыми между перезапусками контейнеров.
Пример:
- Named volumes (как pgdata) хранятся в /var/lib/docker/volumes/ и управляются Docker. При удалении контейнера данные сохраняются.
- Host mounts — явное указание пути на хосте:
Здесь изменения в /var/lib/postgresql/data контейнера отражаются на хосте в ./data. Это работает через механизм *bind mounts* ядра Linux, где inode хоста и контейнера совпадают.
3. networks
Создает изолированные сети для сервисов:
- По умолчанию Compose создает bridge-сеть, где контейнеры общаются через L2 (Ethernet).
- Внутри сети Docker внедряет *виртуальный DNS-сервер* на 127.0.0.11, обрабатывающий запросы к именам сервисов. Например, запрос db резолвится в IP-адрес контейнера db.
Переменные окружения в Compose
Использование .env и ${VAR}
Docker Compose поддерживает подстановку переменных из .env-файла:
В docker-compose.yaml:
- Как это работает: При парсинге YAML Compose ищет .env в текущей директории и заменяет ${VAR} на значения из файла. Если переменная не определена, контейнер запустится с пустым значением (если не указано ? для ошибки).
- Нюанс безопасности: .env не шифруется. Для секретов используйте Docker Secrets или инструменты вроде HashiCorp Vault.
Основы: docker-compose.yaml и его архитектура
Что такое docker-compose.yaml?
Это декларативный конфигурационный файл, управляющий многоконтейнерными приложениями через Docker Compose. В отличие от ручного запуска контейнеров через docker run, Compose абстрагирует сложность оркестрации: один файл определяет сервисы, сети, тома и зависимости. При выполнении docker-compose up Docker Engine создает изолированную среду, где контейнеры взаимодействуют через виртуальные сети, а данные сохраняются в управляемых томах.
Как это работает на уровне ядра?
Когда вы запускаете docker-compose up, Docker Daemon:
1. Парсит YAML-файл и строит граф зависимостей сервисов.
2. Создает изолированные *сетевые пространства имен* (network namespaces) для каждого сервиса, если не указано иное.
3. Назначает виртуальные Ethernet-интерфейсы (veth pairs) для соединения контейнеров в bridge-сеть по умолчанию.
4. Для томов (volumes) монтирует директории хоста или управляемые Docker тома в контейнеры через bind mounts или overlayfs.
Структура docker-compose: services, volumes, networks
1. services
Определяет контейнеры как логические сервисы.
Пример:
services:
web:
image: nginx:alpine
ports:
- "8080:80"
db:
image: postgres:15
environment:
POSTGRES_PASSWORD: example
- image — базовый образ (загружается из Docker Hub, если отсутствует локально).
- ports — проброс портов хоста в контейнер. Формат HOST:CONTAINER. При старте Docker создает iptables-правила для перенаправления трафика.
- Важно: Контейнеры в одной сети Compose общаются по имени сервиса (например, web может обращаться к db по DNS-имени db:5432). Это достигается через встроенный DNS-резолвер Docker, который динамически обновляет записи при старте/остановке сервисов.
2. volumes
Управляет данными, сохраняемыми между перезапусками контейнеров.
Пример:
volumes:
pgdata:
driver: local
- Named volumes (как pgdata) хранятся в /var/lib/docker/volumes/ и управляются Docker. При удалении контейнера данные сохраняются.
- Host mounts — явное указание пути на хосте:
volumes:
- ./data:/var/lib/postgresql/data
Здесь изменения в /var/lib/postgresql/data контейнера отражаются на хосте в ./data. Это работает через механизм *bind mounts* ядра Linux, где inode хоста и контейнера совпадают.
3. networks
Создает изолированные сети для сервисов:
networks:
app_net:
driver: bridge
- По умолчанию Compose создает bridge-сеть, где контейнеры общаются через L2 (Ethernet).
- Внутри сети Docker внедряет *виртуальный DNS-сервер* на 127.0.0.11, обрабатывающий запросы к именам сервисов. Например, запрос db резолвится в IP-адрес контейнера db.
Переменные окружения в Compose
Использование .env и ${VAR}
Docker Compose поддерживает подстановку переменных из .env-файла:
DB_PASSWORD=secure_password
В docker-compose.yaml:
services:
db:
environment:
POSTGRES_PASSWORD: ${DB_PASSWORD}
- Как это работает: При парсинге YAML Compose ищет .env в текущей директории и заменяет ${VAR} на значения из файла. Если переменная не определена, контейнер запустится с пустым значением (если не указано ? для ошибки).
- Нюанс безопасности: .env не шифруется. Для секретов используйте Docker Secrets или инструменты вроде HashiCorp Vault.
👍3
Оркестрация зависимостей и профили
depends_on, restart, healthcheck
Проблема depends_on
- depends_on гарантирует, что контейнер db запустится раньше app, но не дожидается готовности сервиса (например, PostgreSQL может быть запущен, но не готов принимать подключения).
- Последствие: Java-приложение может упасть при старте, если БД еще не инициализирована.
Решение через healthcheck
- Docker периодически выполняет команду pg_isready внутри контейнера.
- Как это работает: Внутри контейнера запускается фоновый процесс, проверяющий healthcheck. Результат сохраняется в cgroups (контрольные группы ядра), и Docker Daemon отслеживает статус через /sys/fs/cgroup/.../docker/.../healthcheck.
- Для app:
Теперь app стартует только после успешного прохождения healthcheck db.
Профили (docker-compose.override.yml)
Зачем нужны override-файлы?
Для разделения конфигурации между окружениями (dev, prod).
Пример структуры:
- Как работает: При запуске docker-compose up Compose автоматически применяет docker-compose.override.yml.
Для prod:
- Пример override для dev:
Здесь исходный код монтируется в контейнер для горячей перезагрузки.
Пример: Java + PostgreSQL
docker-compose.yaml
Как это работает в памяти:
1. При старте db инициализирует shared memory для PostgreSQL (через shm_size в cgroups).
2. Том pgdata монтируется в контейнер как *bind mount*, обеспечивая персистентность данных.
3. Сеть app_net создает bridge-интерфейс на хосте (например, br-123456), куда подключаются veth-пары контейнеров.
Ключевой нюанс:
- Если DB_PASSWORD не задан в .env, контейнер db упадет с ошибкой (PostgreSQL требует пароль).
- condition: service_healthy предотвращает race condition между стартом app и инициализацией БД.
depends_on, restart, healthcheck
Проблема depends_on
services:
app:
depends_on:
- db
db:
image: postgres
- depends_on гарантирует, что контейнер db запустится раньше app, но не дожидается готовности сервиса (например, PostgreSQL может быть запущен, но не готов принимать подключения).
- Последствие: Java-приложение может упасть при старте, если БД еще не инициализирована.
Решение через healthcheck
db:
image: postgres
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 5s
timeout: 5s
retries: 10
- Docker периодически выполняет команду pg_isready внутри контейнера.
- Как это работает: Внутри контейнера запускается фоновый процесс, проверяющий healthcheck. Результат сохраняется в cgroups (контрольные группы ядра), и Docker Daemon отслеживает статус через /sys/fs/cgroup/.../docker/.../healthcheck.
- Для app:
app:
depends_on:
db:
condition: service_healthy
Теперь app стартует только после успешного прохождения healthcheck db.
Профили (docker-compose.override.yml)
Зачем нужны override-файлы?
Для разделения конфигурации между окружениями (dev, prod).
Пример структуры:
├── docker-compose.yml
├── docker-compose.override.yml # Для dev
└── docker-compose.prod.yml # Для prod
- Как работает: При запуске docker-compose up Compose автоматически применяет docker-compose.override.yml.
Для prod:
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up
- Пример override для dev:
# docker-compose.override.yml
services:
app:
environment:
DEBUG: "true"
volumes:
- ./src:/app/src
Здесь исходный код монтируется в контейнер для горячей перезагрузки.
Пример: Java + PostgreSQL
docker-compose.yaml
version: '3.8'
services:
app:
build: ./app
ports:
- "8080:8080"
environment:
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/mydb
SPRING_DATASOURCE_PASSWORD: ${DB_PASSWORD}
depends_on:
db:
condition: service_healthy
networks:
- app_net
db:
image: postgres:15
environment:
POSTGRES_DB: mydb
POSTGRES_PASSWORD: ${DB_PASSWORD}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U postgres"]
interval: 2s
timeout: 5s
retries: 15
networks:
- app_net
volumes:
pgdata:
networks:
app_net:
driver: bridge
Как это работает в памяти:
1. При старте db инициализирует shared memory для PostgreSQL (через shm_size в cgroups).
2. Том pgdata монтируется в контейнер как *bind mount*, обеспечивая персистентность данных.
3. Сеть app_net создает bridge-интерфейс на хосте (например, br-123456), куда подключаются veth-пары контейнеров.
Ключевой нюанс:
- Если DB_PASSWORD не задан в .env, контейнер db упадет с ошибкой (PostgreSQL требует пароль).
- condition: service_healthy предотвращает race condition между стартом app и инициализацией БД.
👍4
Расширенный пример: добавляем Kafka
KRaft vs ZooKeeper
В Kafka 3.3+ рекомендуется использовать KRaft (Kafka Raft Metadata mode) вместо ZooKeeper:
- ZooKeeper: Требует отдельного кластера для хранения метаданных, увеличивает сложность.
- KRaft: Метаданные управляются самой Kafka через Raft-протокол. Упрощает настройку и снижает задержки.
docker-compose.yaml для Kafka с KRaft
Критически важные нюансы:
1. `KAFKA_CFG_ADVERTISED_LISTENERS` должен указывать на имя сервиса (kafka), чтобы другие контейнеры могли подключиться. Если использовать localhost, клиенты из других контейнеров не смогут соединиться.
2. Сеть: Kafka требует стабильного DNS-имени. В Compose это достигается через имя сервиса (kafka).
Конфигурация Kafka для Java-клиента
Через Spring-Kafka
- Как это работает: Spring Boot использует KafkaConsumer, который подключается к kafka:9092 через DNS Compose-сети.
Через чистый Kafka Client API
- Важно: Используйте имя сервиса (kafka), а не localhost или IP. Внутри сети Compose DNS резолвит kafka в актуальный IP контейнера.
Рекомендации для продакшена
1. Healthcheck обязателен для сервисов с долгой инициализацией (БД, Kafka). Без него возникают race conditions.
2. Избегайте host mounts в продакшене — используйте named volumes для персистентности и изоляции.
3. KRaft — будущее Kafka. ZooKeeper добавляет избыточную сложность и уязвимости.
4. Сетевые настройки Kafka — ключевой источник ошибок. Убедитесь, что advertised.listeners соответствует DNS-имени сервиса в Compose.
Docker Compose — не инструмент для продакшена (используйте Kubernetes), но он незаменим для локальной разработки микросервисов. Понимание его внутренней работы позволяет избежать подводных камней и строить предсказуемую инфраструктуру.
KRaft vs ZooKeeper
В Kafka 3.3+ рекомендуется использовать KRaft (Kafka Raft Metadata mode) вместо ZooKeeper:
- ZooKeeper: Требует отдельного кластера для хранения метаданных, увеличивает сложность.
- KRaft: Метаданные управляются самой Kafka через Raft-протокол. Упрощает настройку и снижает задержки.
docker-compose.yaml для Kafka с KRaft
services:
kafka:
image: bitnami/kafka:3.5.1
container_name: kafka
ports:
- "9092:9092"
environment:
KAFKA_CFG_NODE_ID: 0
KAFKA_CFG_PROCESS_ROLES: broker,controller
KAFKA_CFG_LISTENERS: PLAINTEXT://:9092,CONTROLLER://:9093
KAFKA_CFG_ADVERTISED_LISTENERS: PLAINTEXT://kafka:9092
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: CONTROLLER
KAFKA_CFG_INTER_BROKER_LISTENER_NAME: PLAINTEXT
networks:
- app_net
app:
# ... (Java-сервис из предыдущего примера)
depends_on:
- kafka
Критически важные нюансы:
1. `KAFKA_CFG_ADVERTISED_LISTENERS` должен указывать на имя сервиса (kafka), чтобы другие контейнеры могли подключиться. Если использовать localhost, клиенты из других контейнеров не смогут соединиться.
2. Сеть: Kafka требует стабильного DNS-имени. В Compose это достигается через имя сервиса (kafka).
Конфигурация Kafka для Java-клиента
Через Spring-Kafka
# application.properties
spring.kafka.bootstrap-servers=kafka:9092
spring.kafka.consumer.group-id=my-group
- Как это работает: Spring Boot использует KafkaConsumer, который подключается к kafka:9092 через DNS Compose-сети.
Через чистый Kafka Client API
Properties props = new Properties();
props.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, "kafka:9092");
props.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
props.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
KafkaProducer<String, String> producer = new KafkaProducer<>(props);
- Важно: Используйте имя сервиса (kafka), а не localhost или IP. Внутри сети Compose DNS резолвит kafka в актуальный IP контейнера.
Рекомендации для продакшена
1. Healthcheck обязателен для сервисов с долгой инициализацией (БД, Kafka). Без него возникают race conditions.
2. Избегайте host mounts в продакшене — используйте named volumes для персистентности и изоляции.
3. KRaft — будущее Kafka. ZooKeeper добавляет избыточную сложность и уязвимости.
4. Сетевые настройки Kafka — ключевой источник ошибок. Убедитесь, что advertised.listeners соответствует DNS-имени сервиса в Compose.
Docker Compose — не инструмент для продакшена (используйте Kubernetes), но он незаменим для локальной разработки микросервисов. Понимание его внутренней работы позволяет избежать подводных камней и строить предсказуемую инфраструктуру.
👍3
Что выведет код?
#Tasks
public class Task080925 {
public static void main(String[] args) {
Long a = 100L;
Long b = 100L;
Long c = 200L;
Long d = 200L;
System.out.println((a == b) + " " + (c == d));
}
}
#Tasks
👍3
👍5
Вопрос с собеседований
Что такое distinct в Stream API?🤓
Ответ:
Метод distinct() удаляет дубликаты из потока на основе equals() и hashCode().
Пример:
List<Integer> unique = Stream.of(1, 2, 2, 3)
.distinct()
.collect(Collectors.toList()); // [1, 2, 3]
Для параллельных потоков использует ConcurrentHashSet внутри.
#собеседование
Что такое distinct в Stream API?
Ответ:
Метод distinct()
Пример:
List<Integer> unique = Stream.of(1, 2, 2, 3)
.distinct()
.collect(Collectors.toList()); // [1, 2, 3]
Для параллельных потоков использует ConcurrentHashSet внутри.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Де́ннис Макалистэйр Ри́тчи (Dennis MacAlistair Ritchie; 9 сентября 1941, Бронксвилл[англ.], штат Нью-Йорк, США — предположительно 8—12 октября 2011, Беркли-Хайтс, США) — американский учёный, создатель языка C и со-разработчик UNIX. Язык C по сей день широко используется для написания приложений и операционных систем, и его влияние наблюдается во многих современных языках программирования. Unix также оказал сильное влияние, основав идеи и принципы, которые сейчас являются прочно устоявшимися в вычислительной технике. Популярные операционные системы GNU/Linux и Mac OS X, а также их инструменты являются потомками работ Ритчи, и ОС Microsoft Windows также включает инструменты для совместимости с Unix и компилятор C для разработчиков.
Ави Вигдерзон (ивр. אבי ויגדרזון, род. 9 сентября 1956, Хайфа) — израильско-американский математик и теоретик информатики, лауреат Turing Award за вклад в теорию вычислительной сложности и алгоритмов.
2001— В 01:46:40 по Гринвичу часы отсчитали миллиардную секунду эры UNIX, которая началась в полночь 1 января 1970 года — именно с этого момента Unix-системы отсчитывают «внутреннее» время.
#Biography #Birth_Date #Events #09Сентября
Please open Telegram to view this post
VIEW IN TELEGRAM
👍1
Основы ООП в Java
Глава 5. Абстракция
Интерфейсы. Default и static методы
Интерфейс в Java — это полностью абстрактный тип, который определяет контракт: набор методов, которые класс должен реализовать. Он описывает "что делать", но не "как". Интерфейсы идеальны для задания общего поведения для несвязанных классов, поддерживая полиморфизм без наследования состояния.
Синтаксис:
Особенности:
Все методы в интерфейсе до Java 8 были неявно public abstract.
Классы реализуют интерфейс через implements.
Один класс может реализовать несколько интерфейсов (множественная реализация).
Нельзя создать объект интерфейса (new Interface() — ошибка).
Интерфейсы: Основы и реализация
Класс, реализующий интерфейс, обязан предоставить реализацию всех его абстрактных методов (или стать abstract).
Пример интерфейса Drawable:
Классы, реализующие Drawable:
Полиморфизм с интерфейсом:
Вывод:
Нюанс: Drawable объединяет несвязанные классы (Circle и Square) через общий контракт.
Default методы: Добавление реализации
С Java 8 интерфейсы могут содержать default методы — методы с реализацией, которые классы наследуют, если не переопределяют. Это позволяет расширять интерфейсы без ломки существующих реализаций.
Синтаксис:
Пример с default методом:
В Circle и Square не нужно реализовывать describe() — он наследуется.
Но можно переопределить:
Зачем default:
Расширение интерфейса без изменения реализующих классов.
Общий код для большинства реализаций.
Нюанс: Если два интерфейса имеют default метод с одинаковой сигнатурой, класс должен явно переопределить его.
#Java #для_новичков #beginner #Abstract #Interfaces
Глава 5. Абстракция
Интерфейсы. Default и static методы
Интерфейс в Java — это полностью абстрактный тип, который определяет контракт: набор методов, которые класс должен реализовать. Он описывает "что делать", но не "как". Интерфейсы идеальны для задания общего поведения для несвязанных классов, поддерживая полиморфизм без наследования состояния.
Синтаксис:
public interface InterfaceName {
// Абстрактные методы
// (с Java 8) default и static методы
}
Особенности:
Все методы в интерфейсе до Java 8 были неявно public abstract.
Классы реализуют интерфейс через implements.
Один класс может реализовать несколько интерфейсов (множественная реализация).
Нельзя создать объект интерфейса (new Interface() — ошибка).
Интерфейсы: Основы и реализация
Класс, реализующий интерфейс, обязан предоставить реализацию всех его абстрактных методов (или стать abstract).
Пример интерфейса Drawable:
public interface Drawable {
void draw(); // Неявно public abstract
}
Классы, реализующие Drawable:
public class Circle implements Drawable {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Рисую круг радиусом " + radius);
}
}
public class Square implements Drawable {
private double side;
public Square(double side) {
this.side = side;
}
@Override
public void draw() {
System.out.println("Рисую квадрат со стороной " + side);
}
}
Полиморфизм с интерфейсом:
public class Main {
public static void main(String[] args) {
Drawable[] shapes = {new Circle(5.0), new Square(4.0)};
for (Drawable shape : shapes) {
shape.draw(); // Вызывает Circle.draw() или Square.draw()
}
}
}
Вывод:
Рисую круг радиусом 5.0
Рисую квадрат со стороной 4.0
Нюанс: Drawable объединяет несвязанные классы (Circle и Square) через общий контракт.
Default методы: Добавление реализации
С Java 8 интерфейсы могут содержать default методы — методы с реализацией, которые классы наследуют, если не переопределяют. Это позволяет расширять интерфейсы без ломки существующих реализаций.
Синтаксис:
default returnType methodName(params) {
// Реализация
}
Пример с default методом:
public interface Drawable {
void draw();
default void describe() {
System.out.println("Это фигура, которую можно нарисовать.");
}
}
В Circle и Square не нужно реализовывать describe() — он наследуется.
Но можно переопределить:
public class Circle implements Drawable {
private double radius;
public Circle(double radius) {
this.radius = radius;
}
@Override
public void draw() {
System.out.println("Рисую круг радиусом " + radius);
}
@Override
public void describe() {
System.out.println("Это круг радиусом " + radius);
}
}
Вызов circle.describe(): "Это круг радиусом 5.0".
Square использует default: "Это фигура, которую можно нарисовать.".
Зачем default:
Расширение интерфейса без изменения реализующих классов.
Общий код для большинства реализаций.
Нюанс: Если два интерфейса имеют default метод с одинаковой сигнатурой, класс должен явно переопределить его.
#Java #для_новичков #beginner #Abstract #Interfaces
👍4
Static методы: Утилиты интерфейса
С Java 8 интерфейсы могут содержать static методы — методы, принадлежащие интерфейсу, а не объекту. Они не наследуются и вызываются через имя интерфейса.
Пример:
Вызов:
Зачем static:
Утилитарные функции, связанные с интерфейсом.
Не требуют объекта, упрощают организацию кода.
Все нюансы интерфейсов
Множественная реализация:
Класс может implements Interface1, Interface2, ....
Нюанс: Конфликт default методов решается переопределением:
Модификаторы:
Абстрактные методы: Неявно public abstract.
Поля: Всегда public static final (константы).
Default/static: Явно public.
Наследование интерфейсов:
Интерфейс может extends другой интерфейс: public interface AdvancedDrawable extends Drawable { ... }.
Ошибки:
Не реализован abstract метод в non-abstract классе — ошибка.
Конфликт default методов без переопределения — ошибка.
Нельзя инстанцировать интерфейс.
Default vs abstract классы:
Интерфейсы: Нет состояния, множественная реализация, default/static методы.
Abstract классы: Могут иметь состояние, конструкторы, одиночное наследование.
Java 9+: Private методы в интерфейсах для общего кода в default/static.
Как создать это в IntelliJ IDEA
Интерфейс: New → Interface → Drawable.
Implements: В классе напишите implements Drawable, IDE предложит override.
Default/static: Напишите default void describe() {} — IDE проверит синтаксис.
Тестирование: Создайте массив Drawable[] и цикл.
Полезные советы для новичков
Используйте интерфейсы для контрактов: Когда нужно поведение без состояния.
Default для совместимости: Добавляйте общую логику, но позволяйте override.
Static для утилит: Организуйте связанные функции.
Избегайте конфликтов: Переопределяйте default методы при множественной реализации.
Ресурсы: Oracle Tutorials on Interfaces.
#Java #для_новичков #beginner #Abstract #Interfaces
С Java 8 интерфейсы могут содержать static методы — методы, принадлежащие интерфейсу, а не объекту. Они не наследуются и вызываются через имя интерфейса.
Пример:
public interface Drawable {
void draw();
default void describe() {
System.out.println("Это фигура, которую можно нарисовать.");
}
static void printInfo() {
System.out.println("Интерфейс Drawable для рисования фигур.");
}
}
Вызов:
Drawable.printInfo(); // Вывод: Интерфейс Drawable для рисования фигур.
Нюанс: Circle.printInfo() — ошибка, только Drawable.printInfo().
Зачем static:
Утилитарные функции, связанные с интерфейсом.
Не требуют объекта, упрощают организацию кода.
Все нюансы интерфейсов
Множественная реализация:
Класс может implements Interface1, Interface2, ....
Нюанс: Конфликт default методов решается переопределением:
public class MyClass implements Interface1, Interface2 {
@Override
public void method() {
Interface1.super.method(); // Выбор реализации
}
}
Модификаторы:
Абстрактные методы: Неявно public abstract.
Поля: Всегда public static final (константы).
Default/static: Явно public.
Наследование интерфейсов:
Интерфейс может extends другой интерфейс: public interface AdvancedDrawable extends Drawable { ... }.
Ошибки:
Не реализован abstract метод в non-abstract классе — ошибка.
Конфликт default методов без переопределения — ошибка.
Нельзя инстанцировать интерфейс.
Default vs abstract классы:
Интерфейсы: Нет состояния, множественная реализация, default/static методы.
Abstract классы: Могут иметь состояние, конструкторы, одиночное наследование.
Java 9+: Private методы в интерфейсах для общего кода в default/static.
Как создать это в IntelliJ IDEA
Интерфейс: New → Interface → Drawable.
Implements: В классе напишите implements Drawable, IDE предложит override.
Default/static: Напишите default void describe() {} — IDE проверит синтаксис.
Тестирование: Создайте массив Drawable[] и цикл.
Полезные советы для новичков
Используйте интерфейсы для контрактов: Когда нужно поведение без состояния.
Default для совместимости: Добавляйте общую логику, но позволяйте override.
Static для утилит: Организуйте связанные функции.
Избегайте конфликтов: Переопределяйте default методы при множественной реализации.
Ресурсы: Oracle Tutorials on Interfaces.
#Java #для_новичков #beginner #Abstract #Interfaces
👍4
Что выведет код?
#Tasks
interface Calculator090925 {
static int multiply(int a, int b) {
return a * b;
}
default int add(int a, int b) {
return a + b;
}
}
class Math090925 implements Calculator090925 {}
public class Task090925 {
public static void main(String[] args) {
Math090925 math = new Math090925();
System.out.println(math.add(2, 3));
System.out.println(Math.multiply(2, 3));
}
}
#Tasks
👍3
👍4
Вопрос с собеседований
Что такое NumberFormatException?🤓
Ответ:
Возникает при попытке преобразовать строку в число в неверном формате.
Пример:
Integer.parseInt("abc"); // NumberFormatException
Используйте try-catch или валидацию.
#собеседование
Что такое NumberFormatException?
Ответ:
Пример:
Integer.parseInt("abc"); // NumberFormatException
Используйте try-catch или валидацию.
#собеседование
Please open Telegram to view this post
VIEW IN TELEGRAM
👍6
Чарльз Си́мони (англ. Charles Simonyi), при рождении Ка́рой Ши́моньи (венг. Simonyi Károly; род. 10 сентября 1948, Будапешт) — венгеро-американский разработчик и архитектор приложений (ключевая роль в разработке Microsoft Word/Excel), пионер WYSIWYG-редакторов.
2008 — запуск первого циркуляционного «первого луча» Большого адронного коллайдера (LHC).
#Biography #Birth_Date #Events #10Сентября
Please open Telegram to view this post
VIEW IN TELEGRAM
👍4
Production практики для Java в Docker
Управление сборкой и архитектурой
Build args (ARG vs ENV): передача параметров при сборке
- ARG — переменная, доступная только на этапе сборки образа. Не сохраняется в финальном образе.
- ENV — переменная, доступная во время выполнения контейнера. Сохраняется в образе.
Как это работает:
- При выполнении docker build --build-arg JAVA_VERSION=17 . значение JAVA_VERSION передается в Dockerfile через ARG.
- Если ARG используется в ENV, его значение копируется в образ:
Нюансы безопасности:
- Никогда не передавайте секреты через ARG без флага --secret (например, токены для приватных репозиториев):
В Dockerfile:
- Почему это важно: Секреты, переданные через ARG, попадают в историю образа (видны в docker history).
Пример для Maven:
- MAVEN_VERSION используется только при загрузке Maven и не сохраняется в образе.
Multi-arch образы (ARM64 vs x86) и кросс-сборка
Образы, собранные для разных архитектур процессоров (x86_64, ARM64), чтобы запускаться на любом устройстве — от серверов до Raspberry Pi.
Как это работает:
- Docker использует QEMU для эмуляции архитектур через binfmt_misc (механизм ядра Linux).
- Buildx — расширение Docker CLI для кросс-платформенной сборки:
- При пушинге в реестр создается manifest list — файл, описывающий образы для разных архитектур.
Нюансы:
- Эмуляция через QEMU:
- Требует установки qemu-user-static:
- Замедляет сборку на 20-30% из-за эмуляции.
- Сборка нативно:
- Для ARM64 используйте серверы с ARM-процессорами (AWS Graviton, Raspberry Pi).
- В CI/CD настройте параллельную сборку для каждой архитектуры.
Пример для Java:
- Переменная TARGETARCH автоматически устанавливается Buildx (значения: amd64, arm64).
Использование .dockerignore для ускорения сборки
Файл, исключающий ненужные директории/файлы из build context — набора данных, передаваемых Docker Daemon при сборке.
Почему это критично:
- Build context отправляется в память Docker Daemon через сокет.
- Если в контексте есть target/ (500 МБ), сборка замедлится из-за передачи данных.
Типичное содержимое .dockerignore:
Как это работает:
1. При docker build . CLI сканирует текущую директорию.
2. Файлы из .dockerignore исключаются из контекста.
3. Оставшиеся данные архивируются и отправляются в память Docker Daemon (через /var/run/docker.sock).
Нюанс:
- .dockerignore не влияет на COPY в Dockerfile. Если файл исключен из контекста, COPY завершится ошибкой.
#Java #middle #Docker #Production
Управление сборкой и архитектурой
Build args (ARG vs ENV): передача параметров при сборке
- ARG — переменная, доступная только на этапе сборки образа. Не сохраняется в финальном образе.
- ENV — переменная, доступная во время выполнения контейнера. Сохраняется в образе.
Как это работает:
- При выполнении docker build --build-arg JAVA_VERSION=17 . значение JAVA_VERSION передается в Dockerfile через ARG.
- Если ARG используется в ENV, его значение копируется в образ:
ARG JAVA_VERSION=17
ENV JAVA_VERSION=${JAVA_VERSION}
Здесь JAVA_VERSION станет частью образа и будет доступна в runtime.
Нюансы безопасности:
- Никогда не передавайте секреты через ARG без флага --secret (например, токены для приватных репозиториев):
docker build --secret id=github,src=./github.token .
В Dockerfile:
RUN --mount=type=secret,id=github \
git clone https://$(cat /run/secrets/github)@github.com/private/repo.git
- Почему это важно: Секреты, переданные через ARG, попадают в историю образа (видны в docker history).
Пример для Maven:
ARG MAVEN_VERSION=3.8.6
ARG USER_HOME_DIR="/root"
RUN mkdir -p /usr/share/maven && \
curl -fsSL "https://archive.apache.org/dist/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz" \
| tar -xzC /usr/share/maven --strip-components=1
- MAVEN_VERSION используется только при загрузке Maven и не сохраняется в образе.
Multi-arch образы (ARM64 vs x86) и кросс-сборка
Образы, собранные для разных архитектур процессоров (x86_64, ARM64), чтобы запускаться на любом устройстве — от серверов до Raspberry Pi.
Как это работает:
- Docker использует QEMU для эмуляции архитектур через binfmt_misc (механизм ядра Linux).
- Buildx — расширение Docker CLI для кросс-платформенной сборки:
docker buildx create --use
docker buildx build --platform linux/amd64,linux/arm64 -t myapp:latest .
- При пушинге в реестр создается manifest list — файл, описывающий образы для разных архитектур.
Нюансы:
- Эмуляция через QEMU:
- Требует установки qemu-user-static:
docker run --rm --privileged multiarch/qemu-user-static --reset -p yes
- Замедляет сборку на 20-30% из-за эмуляции.
- Сборка нативно:
- Для ARM64 используйте серверы с ARM-процессорами (AWS Graviton, Raspberry Pi).
- В CI/CD настройте параллельную сборку для каждой архитектуры.
Пример для Java:
# Сборка под ARM64 и x86_64
FROM --platform=$BUILDPLATFORM openjdk:17 AS builder
ARG TARGETARCH
RUN echo "Собираем под архитектуру: ${TARGETARCH}"
COPY . .
RUN ./mvnw package -DskipTests
- Переменная TARGETARCH автоматически устанавливается Buildx (значения: amd64, arm64).
Использование .dockerignore для ускорения сборки
Файл, исключающий ненужные директории/файлы из build context — набора данных, передаваемых Docker Daemon при сборке.
Почему это критично:
- Build context отправляется в память Docker Daemon через сокет.
- Если в контексте есть target/ (500 МБ), сборка замедлится из-за передачи данных.
Типичное содержимое .dockerignore:
.git
.gitignore
**/target/
**/*.log
**/.idea/
**/.vscode/
docker-compose.yml
Как это работает:
1. При docker build . CLI сканирует текущую директорию.
2. Файлы из .dockerignore исключаются из контекста.
3. Оставшиеся данные архивируются и отправляются в память Docker Daemon (через /var/run/docker.sock).
Нюанс:
- .dockerignore не влияет на COPY в Dockerfile. Если файл исключен из контекста, COPY завершится ошибкой.
#Java #middle #Docker #Production
👍4
Оптимизация образов: безопасность и размер
jlink: минимальная JVM под конкретное приложение
Инструмент JDK для создания настраиваемой JVM, включающей только необходимые модули (например, java.base, java.logging).
Как это работает:
1. Анализ зависимостей через jdeps:
2. Сборка минимальной JVM:
Результат:
- Стандартный образ OpenJDK 17 — 450 МБ.
- Образ с jlink — 120 МБ.
Нюансы:
- Некоторые библиотеки (например, Hibernate) требуют модуля jdk.unsupported.
- Проверяйте совместимость через --dry-run.
Distroless runtime образы (Google)
Образы без операционной системы, содержащие только JVM и ваше приложение. Созданы Google для минимизации attack surface.
Пример:
Как это работает:
- Distroless использует scratch (пустой образ) и добавляет только:
- Мусоросборщик (glibc или musl)
- Базовые сертификаты SSL,
- Минимальный набор библиотек для JVM
- Нет shell (/bin/sh), поэтому docker exec -it bash невозможен.
Преимущества:
- Размер образа — 80 МБ против 450 МБ у OpenJDK.
- Нулевой attack surface: нет curl, bash, apt для эксплуатации.
Отладка:
- Для диагностики используйте sidecar-контейнеры с curl или netcat.
- Логи читайте через docker logs, метрики — через /actuator/prometheus.
Non-root user: запуск Java-приложений безопасно
Запуск процесса Java не от root, чтобы ограничить ущерб при компрометации контейнера.
Как это работает:
Нюансы безопасности:
- UID/GID:
- Используйте фиксированный UID (1001), чтобы избежать конфликтов с томами.
- Если том смонтирован с правами root, пользователь 1001 не сможет писать в него.
- Capabilities:
- По умолчанию контейнер получает capabilities NET_BIND_SERVICE (привязка к портам < 1024).
- Для запуска на порту 80:
Критическая ошибка:
- Если приложение требует записи в /tmp, убедитесь, что директория доступна для записи:
#Java #middle #Docker #Debug
jlink: минимальная JVM под конкретное приложение
Инструмент JDK для создания настраиваемой JVM, включающей только необходимые модули (например, java.base, java.logging).
Как это работает:
1. Анализ зависимостей через jdeps:
jdeps --print-module-deps target/myapp.jar
Вывод: java.base,java.logging,java.xml.
2. Сборка минимальной JVM:
RUN jlink \
--add-modules java.base,java.logging,java.xml \
--output /jlinked \
--strip-debug \ — удаление отладочной информации.
--compress 2 \ — максимальное сжатие (уменьшает размер на 30%).
--no-header-files \
--no-man-pages
Результат:
- Стандартный образ OpenJDK 17 — 450 МБ.
- Образ с jlink — 120 МБ.
Нюансы:
- Некоторые библиотеки (например, Hibernate) требуют модуля jdk.unsupported.
- Проверяйте совместимость через --dry-run.
Distroless runtime образы (Google)
Образы без операционной системы, содержащие только JVM и ваше приложение. Созданы Google для минимизации attack surface.
Пример:
FROM gcr.io/distroless/java17-debian11
COPY --from=builder /app/target/myapp.jar /app.jar
ENTRYPOINT ["/app.jar"]
Как это работает:
- Distroless использует scratch (пустой образ) и добавляет только:
- Мусоросборщик (glibc или musl)
- Базовые сертификаты SSL,
- Минимальный набор библиотек для JVM
- Нет shell (/bin/sh), поэтому docker exec -it bash невозможен.
Преимущества:
- Размер образа — 80 МБ против 450 МБ у OpenJDK.
- Нулевой attack surface: нет curl, bash, apt для эксплуатации.
Отладка:
- Для диагностики используйте sidecar-контейнеры с curl или netcat.
- Логи читайте через docker logs, метрики — через /actuator/prometheus.
Non-root user: запуск Java-приложений безопасно
Запуск процесса Java не от root, чтобы ограничить ущерб при компрометации контейнера.
Как это работает:
# Создаем пользователя с UID 1001
RUN adduser --uid 1001 --disabled-password --gecos "" appuser
USER 1001
COPY --chown=1001:1001 target/myapp.jar /app.jar
ENTRYPOINT ["java", "-jar", "/app.jar"]
Нюансы безопасности:
- UID/GID:
- Используйте фиксированный UID (1001), чтобы избежать конфликтов с томами.
- Если том смонтирован с правами root, пользователь 1001 не сможет писать в него.
- Capabilities:
- По умолчанию контейнер получает capabilities NET_BIND_SERVICE (привязка к портам < 1024).
- Для запуска на порту 80:
# docker-compose.yml
app:
cap_add:
- NET_BIND_SERVICE
Критическая ошибка:
- Если приложение требует записи в /tmp, убедитесь, что директория доступна для записи:
RUN mkdir /app/tmp && chown 1001:1001 /app/tmp
ENV JAVA_OPTS="-Djava.io.tmpdir=/app/tmp"
#Java #middle #Docker #Debug
👍3
Минимизация attack surface (удаление ненужных слоёв)
Техники:
1. Многоступенчатая сборка (multi-stage build):
Промежуточные слои (Maven, исходники) не попадают в финальный образ.
2. Очистка кэша в том же слое:
- Если очистка в отдельном слое, размер образа не уменьшится (слой сохранится в истории).
3. Удаление ненужных файлов:
- Некоторые модули (CORBA, RMI) не используются в современных приложениях.
Performance tuning: настройка под контейнеры
CPU/Memory ограничения (--cpus, --memory)
Как это работает:
- Docker использует cgroups v2 для ограничения ресурсов:
- -m 512m — лимит памяти (записывается в /sys/fs/cgroup/memory.max),
- --cpus=1.5 — лимит CPU (эквивалентно --cpu-period=100000 --cpu-quota=150000).
Нюансы для JVM:
- По умолчанию JVM не видит лимиты cgroups и выделяет память, равную объему хоста.
- Решение:
Проверка лимитов:
Thread-pool tuning под контейнеры
Проблема:
- Стандартные пулы потоков (например, ForkJoinPool) используют Runtime.getRuntime().availableProcessors(),
- В контейнере это значение равно количеству CPU хоста, а не лимиту (--cpus).
Решение:
1. Для Spring Boot:
2. Для ванильного Java:
Как получить лимит CPU:
JVM-параметры для контейнеров
Критические параметры:
Объяснение:
- -XX:+UseContainerSupport (включено по умолчанию в JDK 8u191+):
- Позволяет JVM читать лимиты из cgroups.
- Без него -XX:MaxRAMPercentage игнорируется.
- -XX:MaxRAMPercentage=75.0:
- Heap = 75% от лимита памяти контейнера.
- Оставшиеся 25% — для Metaspace, native-памяти, стеков потоков.
- -XX:+UseZGC:
- Низколатентный GC (макс. задержки ~1 мс).
- Альтернативы: Shenandoah (JDK 12+), G1GC (по умолчанию).
- -Djava.security.egd=file:/dev/./urandom:
- Ускоряет старт приложений, заменяя блокирующий /dev/random на неблокирующий /dev/urandom.
Проверка работы GC:
- Столбец ZGC покажет использование памяти и паузы сборки мусора.
#Java #middle #Docker #Debug
Техники:
1. Многоступенчатая сборка (multi-stage build):
# Stage 1: Сборка
FROM maven:3.8.6 AS builder
COPY . .
RUN mvn package
# Stage 2: Финальный образ
FROM openjdk:17-jre-slim
COPY --from=builder target/myapp.jar /app.jar
Промежуточные слои (Maven, исходники) не попадают в финальный образ.
2. Очистка кэша в том же слое:
RUN apt-get update && \
apt-get install -y curl && \
rm -rf /var/lib/apt/lists/*
- Если очистка в отдельном слое, размер образа не уменьшится (слой сохранится в истории).
3. Удаление ненужных файлов:
RUN rm -rf $JAVA_HOME/jmods/java.corba.jmod
- Некоторые модули (CORBA, RMI) не используются в современных приложениях.
Performance tuning: настройка под контейнеры
CPU/Memory ограничения (--cpus, --memory)
Как это работает:
- Docker использует cgroups v2 для ограничения ресурсов:
docker run -m 512m --cpus=1.5 myapp
- -m 512m — лимит памяти (записывается в /sys/fs/cgroup/memory.max),
- --cpus=1.5 — лимит CPU (эквивалентно --cpu-period=100000 --cpu-quota=150000).
Нюансы для JVM:
- По умолчанию JVM не видит лимиты cgroups и выделяет память, равную объему хоста.
- Решение:
environment:
JAVA_TOOL_OPTIONS: "-XX:MaxRAMPercentage=75.0" //указывает JVM использовать 75% от лимита cgroups.
Проверка лимитов:
docker exec myapp cat /sys/fs/cgroup/memory.max
docker exec myapp cat /sys/fs/cgroup/cpu.max
Thread-pool tuning под контейнеры
Проблема:
- Стандартные пулы потоков (например, ForkJoinPool) используют Runtime.getRuntime().availableProcessors(),
- В контейнере это значение равно количеству CPU хоста, а не лимиту (--cpus).
Решение:
1. Для Spring Boot:
# application.properties
spring.task.execution.pool.core-size=4
spring.task.execution.pool.max-size=8
2. Для ванильного Java:
int availableCpus = (int) Math.min(
Runtime.getRuntime().availableProcessors(),
Double.parseDouble(System.getenv("CPU_LIMIT"))
);
ExecutorService executor = Executors.newFixedThreadPool(availableCpus * 2);
Как получить лимит CPU:
public static int getContainerCpuLimit() {
try (BufferedReader reader = new BufferedReader(
new FileReader("/sys/fs/cgroup/cpu.max"))) {
String line = reader.readLine();
String[] parts = line.split(" ");
long quota = Long.parseLong(parts[0]);
long period = Long.parseLong(parts[1]);
return (int) (quota / period); // Например, 1.5 → 1
}
}
JVM-параметры для контейнеров
Критические параметры:
environment:
JAVA_TOOL_OPTIONS: >-
-XX:+UseContainerSupport
-XX:MaxRAMPercentage=75.0
-XX:+UseZGC
-Djava.security.egd=file:/dev/./urandom
Объяснение:
- -XX:+UseContainerSupport (включено по умолчанию в JDK 8u191+):
- Позволяет JVM читать лимиты из cgroups.
- Без него -XX:MaxRAMPercentage игнорируется.
- -XX:MaxRAMPercentage=75.0:
- Heap = 75% от лимита памяти контейнера.
- Оставшиеся 25% — для Metaspace, native-памяти, стеков потоков.
- -XX:+UseZGC:
- Низколатентный GC (макс. задержки ~1 мс).
- Альтернативы: Shenandoah (JDK 12+), G1GC (по умолчанию).
- -Djava.security.egd=file:/dev/./urandom:
- Ускоряет старт приложений, заменяя блокирующий /dev/random на неблокирующий /dev/urandom.
Проверка работы GC:
docker exec myapp jstat -gcutil 1 1000
- Столбец ZGC покажет использование памяти и паузы сборки мусора.
#Java #middle #Docker #Debug
👍4
Debug и мониторинг: production-ready диагностика
docker exec, attach к процессу
Стандартные команды:
Нюансы:
- В distroless-образах нет jstack/jstat.
Решение:
- Используйте sidecar-контейнер с OpenJDK:
- Или добавьте инструменты в образ через jlink:
JMX/JFR в контейнере
Настройка JMX:
Проблемы:
- RMI-порт: JMX использует динамические порты для RMI. Чтобы зафиксировать их:
- DNS-имя:
-Djava.rmi.server.hostname=myapp — имя сервиса в Docker Compose.
JFR (Java Flight Recorder):
Интеграция с Prometheus/Grafana
Шаги:
1. Добавьте Micrometer в приложение:
2. Настройте эндпоинт:
3. Docker Compose:
4. prometheus.yml:
Ключевые метрики:
- jvm_memory_used_bytes — использование heap,
- http_server_requests_seconds_count — количество HTTP-запросов,
- jvm_gc_pause_seconds — длительность GC-пауз.
#Java #middle #Docker #Debug
docker exec, attach к процессу
Стандартные команды:
# Стэк-трейс
docker exec myapp jstack 1 > thread_dump.txt
# Мониторинг GC
docker exec myapp jstat -gcutil 1 1000
# Heap-дамп
docker exec myapp jcmd 1 GC.heap_dump /tmp/heap.hprof
Нюансы:
- В distroless-образах нет jstack/jstat.
Решение:
- Используйте sidecar-контейнер с OpenJDK:
debug-tools:
image: openjdk:17
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: ["jstack", "myapp"]
- Или добавьте инструменты в образ через jlink:
RUN jlink \
--add-modules jdk.jfr,jdk.management \
--output /jlinked
JMX/JFR в контейнере
Настройка JMX:
environment:
JAVA_TOOL_OPTIONS: >-
-Dcom.sun.management.jmxremote.port=9090
-Dcom.sun.management.jmxremote.rmi.port=9090
-Dcom.sun.management.jmxremote.ssl=false
-Dcom.sun.management.jmxremote.authenticate=false
-Djava.rmi.server.hostname=myapp
ports:
- "9090:9090"
Проблемы:
- RMI-порт: JMX использует динамические порты для RMI. Чтобы зафиксировать их:
-Dcom.sun.management.jmxremote.rmi.port=9090
- DNS-имя:
-Djava.rmi.server.hostname=myapp — имя сервиса в Docker Compose.
JFR (Java Flight Recorder):
# Запуск записи
docker exec myapp jcmd 1 JFR.start name=recording duration=60s filename=/tmp/recording.jfr
# Экспорт метрик в Prometheus
RUN jcmd 1 JFR.configure stackdepth=128
Интеграция с Prometheus/Grafana
Шаги:
1. Добавьте Micrometer в приложение:
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
2. Настройте эндпоинт:
management.endpoints.web.exposure.include=prometheus
3. Docker Compose:
services:
app:
ports:
- "8080:8080"
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
ports:
- "9090:9090"
4. prometheus.yml:
scrape_configs:
- job_name: 'java'
metrics_path: '/actuator/prometheus'
static_configs:
- targets: ['app:8080']
Ключевые метрики:
- jvm_memory_used_bytes — использование heap,
- http_server_requests_seconds_count — количество HTTP-запросов,
- jvm_gc_pause_seconds — длительность GC-пауз.
#Java #middle #Docker #Debug
👍4
Что выведет код?
#Tasks
public class Task100925 {
public static void main(String[] args) {
int[] a = {1, 2, 3};
int[] b = a;
b[0] = 10;
System.out.println(a[0]);
}
}
#Tasks
👍3