JIT-компилятор (Just-In-Time)
Что это и как работает
- JVM сначала интерпретирует байткод. Горячие участки (часто вызываемые методы/циклы) профилируются и JIT компилирует их в нативный код.
- Tiered Compilation (по умолчанию):
C1 (быстро, меньше оптимизаций) → C2 (медленнее компиляция, агрессивные оптимизации). Возможен OSR (On-Stack Replacement) — “пересадка” горячего цикла на скомпилированный код прямо посреди выполнения. - Ключевые оптимизации: inlining (встраивание вызовов), escape analysis (раскладывание по полям/скаляризация, размещение на стеке), узкоспециализированные intrinsics (
System.arraycopy,Math.*), loop unrolling, vectorization.
Как наблюдать и управлять
Флаги (для локального анализа):
-XX:+PrintCompilation
-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
-XX:CompileThreshold=10000 # порог "горячести"
-XX:TieredStopAtLevel=1..4 # ограничить уровни Tiered
-XX:+DoEscapeAnalysis # обычно включеноТипичные ошибки
- Сравнивают производительность “холодного” кода без прогрева.
- Пишут микробенчмарки без JMH ⇒ меряют JIT/GC, а не код.
- Пессимизируют JIT: частые виртуальные вызовы, не финальные классы/методы, чрезмерная абстракция мешают inlining.
Best practices по JIT
- Прогревай код перед измерениями (или используй JMH).
- Ключевые горячие методы делай маленькими и “inline-friendly”.
- Избегай лишней boxing/unboxing в горячих путях, особенно в
Streamс автопакетированием. - Кэшируй вычисления, убирай полиморфизм там, где важен inlining (sealed/final).
Память JVM: Heap, Stack, Metaspace (+ ещё области)
Карта памяти
- Heap — объекты. Делится на Young (Eden + Survivor S0/S1) и Old.
Быстрое выделение через TLAB (Thread-Local Allocation Buffer) и “bump-the-pointer”. - Stack — кадры вызовов для каждого потока: локальные переменные, ссылки, адрес возврата.
- Metaspace — метаданные классов (замена PermGen с Java 8). Растёт в native-памяти.
Контролируется-XX:MaxMetaspaceSize. - Code Cache — скомпилированный JIT-код (
-XX:ReservedCodeCacheSize). - Direct memory (off-heap NIO) — управляется ОС (
-XX:MaxDirectMemorySize).
Быстрая настройка размеров
-Xms2g -Xmx2g # min/max heap
-XX:MaxMetaspaceSize=512m # верхняя граница Metaspace
-XX:ReservedCodeCacheSize=256mСборщики мусора (очень кратко)
- G1 (по умолчанию в 17+) — региональный, предсказуемые паузы, хороший дефолт.
- ZGC / Shenandoah — низкие паузы, большие heaps, продвинутые (если нужна минимальная latency).
- Общие понятия: Stop-the-World, safepoints, card table, write barriers.
Типичные ошибки
- Маленький heap ⇒ частые GC-паузы, OutOfMemoryError.
- Утечки через статические коллекции/кэши или слушатели (refs не снимаются).
- Не учитывают off-heap (DirectByteBuffer, память нативных драйверов) и винят heap.
Best practices по памяти
- Начинай с G1, убедись, что размер heap разумный (минимум = максимум для стабильности).
- Мониторь метаспейс и direct memory.
- Используй профилирование allocations; уменьшай краткоживущие объекты в горячем коде.
- Для кэшей — soft/weak ссылки по необходимости, ограничивай размер.
Инструменты диагностики
1. jconsole (простая телеметрия через JMX)
- Запуск:
jconsole→ выбрать процесс (локально) или подключиться по JMX. - Что смотреть:
- Memory: графики heap/metaspace, кнопка Perform GC (только для диагностики).
- Threads: состояния потоков, Detect Deadlock.
- MBeans: параметры приложений/пулов (если экспонированы).
- Когда полезен: первое приближение — увидеть рост heap, количество GC, deadlock в один клик.
2. jvisualvm (GUI-профилировщик/анализ дампов)
- Запуск:
jvisualvm(или VisualVM отдельной установкой). - Функции:
- Sampler CPU/Memory (меньше overhead) и Profiler (точнее, но тяжелей).
- Открыть heap dump (
*.hprof), посмотреть Top Consumers (кто ест память), инспектировать граф достижимости. - Плагины: VisualGC (мотивирующий граф поколений heap).
- Когда полезен: найти утечки, горячие методы, подтвердить гипотезы до/после фикса.
3. jmap (heap-снимок и гистограмма)
Основные команды:
jmap -histo <PID> # топ классов по числу/объёму
jmap -histo:live <PID> # только живые объекты (после GC)
jmap -dump:format=b,file=heap.hprof <PID> # дамп heap- Использование: посмотреть, чем забит heap сейчас; выгрузить дамп и открыть в VisualVM/YourKit/JProfiler.
4. jstack (thread dump)
jstack -l <PID> # снять дамп потоков с мониторами
jstack -m <PID> # со стеком нативного кодаЧто искать:
- Потоки в BLOCKED (монитор/lock), WAITING/TIMED_WAITING (ожидание), RUNNABLE (CPU).
- Циклические захваты (deadlock): в конце дампа обычно есть секция “Found one Java-level deadlock”.
- “Hot thread”: поток, который постоянно в RUNNABLE с одной и той же трассой.
Полезное дополнение (не просили, но маст-хэв):
jcmd <PID> VM.native_memory summary— сводка по native/метаспейсу;
jcmd <PID> GC.heap_info | GC.class_histogram | Thread.print.
Мини-кейсы (код → как диагностировать)
1. Утечка памяти через статический список
public class Leak {
private static final List<byte[]> bag = new ArrayList<>();
public static void main(String[] args) throws Exception {
while (true) {
bag.add(new byte[1_000_000]); // 1 MB
Thread.sleep(50);
}
}
}Диагностика:
jconsole→ Memory растёт ступеньками, GC не помогает → подозрение на удержание.jmap -histo:live <PID>→ многоbyte[].jmap -dump:format=b,file=heap.hprof <PID>→ открыть в VisualVM, найти GC Roots → увидишь статическое полеLeak.bag. Фикс: ограничить/очищать кэш, использовать WeakReference/SoftReference при необходимости.
2. Deadlock из-за порядка блокировок
class Locks {
private final Object a = new Object();
private final Object b = new Object();
void m1() { synchronized (a) { sleep(100); synchronized (b) {} } }
void m2() { synchronized (b) { sleep(100); synchronized (a) {} } }
}Диагностика:
jconsole→ Threads → Detect Deadlock.jstack -l <PID>→ секция “Found one Java-level deadlock” с перечислением нитей и владельцев мониторов. Фикс: строгий порядок захвата (всегдаaзатемb), илиtryLock/таймауты, или более крупноблочная синхронизация.
3. Высокий CPU одной нитью
Симптом: 100% ядра.
Диагностика:
top/htop→ PID → tid нити с высоким CPU.jstack <PID>несколько раз подряд → одна и та же трасса в RUNNABLE ⇒ горячая точка.- Дальше jvisualvm CPU Sampler/Profiler для подтверждения.
Чек-лист поиска проблем (быстрый)
- Память:
jconsoleграфики →jmap -histo:live→ heap dump → VisualVM. - Паузы/GC: включить GC-логи (
-Xlog:gc*в 17+), проверить коллектор/размер heap. - CPU:
jstackнесколько раз → hot method →jvisualvmSampler. - Блокировки:
jconsoleDetect Deadlock →jstack -l. - Native/Metaspace:
jcmd VM.native_memory summary,-XX:MaxMetaspaceSize.
Best practices (итого)
- Для продакшена: G1, адекватный
-Xms = -Xmx, лог GC (-Xlog:gc*:file=gc.log). - Для анализа: VisualVM/
jconsoleдля первых симптомов, jmap/jstack для фактов. - Уменьшай аллоцирование в горячих местах, избегай лишнего boxing и временных объектов.
- Проектируй блокировки: порядок, таймауты,
Lock/StampedLockвчитку. - Покрывай микробенчмарки JMH, а не самопальными таймерами.
- Следи за Metaspace/Code Cache/Direct memory — это тоже падения по OOME.
- Для кэшей — лимиты и weak/soft ссылки где уместно.