JIT Compiler (Just-In-Time)
What it is and how it works
- JVM first interprets bytecode. Hot spots (frequently called methods/loops) are profiled, and the JIT compiler compiles them into native code.
- Tiered Compilation (default): C1 (fast, fewer optimizations) → C2 (slower compilation, aggressive optimizations). OSR (On-Stack Replacement) is possible – “re-hosting” a hot loop onto compiled code directly in the middle of execution.
- Key optimizations: inlining (inline calls), escape analysis (field/scalarization, stack placement), narrowly specialized intrinsics (
System.arraycopy,Math.*), loop unrolling, vectorization.
How to observe and manage
-XX:+PrintCompilation
-XX:+UnlockDiagnosticVMOptions -XX:+PrintInlining
-XX:CompileThreshold=10000 # "hotness" threshold
-XX:TieredStopAtLevel=1..4 # limit tiered levels
-XX:+DoEscapeAnalysis # usually enabledTypical errors
- Compares performance of “cold” code without warm-up.
- Writes microbenchmarks without JMH ⇒ measures JIT/GC, not code.
- Pessimizes JIT: frequent virtual calls, non-final classes/methods, excessive abstraction hinder inlining.
Best practices for JIT
- Warm up code before measurements (or use JMH).
- Make key hot methods small and “inline-friendly”.
- Avoid unnecessary boxing/unboxing in hot paths, especially in
Streamwith auto-boxing. - Cache computations, remove polymorphism where inlining is important (sealed/final).
JVM Memory: Heap, Stack, Metaspace (+ other areas)
Memory Map
- Heap — objects. Divided into Young (Eden + Survivor S0/S1) and Old. Fast allocation via TLAB (Thread-Local Allocation Buffer) and “bump-the-pointer”.
- Stack — call frames for each thread: local variables, references, return address.
- Metaspace — class metadata (replacement for PermGen in Java 8). Grows in native memory.
Controlled by
-XX:MaxMetaspaceSize. - Code Cache — compiled JIT code (
-XX:ReservedCodeCacheSize). - Direct memory (off-heap NIO) — managed by the OS (
-XX:MaxDirectMemorySize).
Quick Size Setup
-Xms2g -Xmx2g
-XX:MaxMetaspaceSize=512m
-XX:ReservedCodeCacheSize=256mGarbage Collectors (very briefly)
- G1 (default in 17+) — regional, predictable pauses, good default.
- ZGC / Shenandoah — low pauses, large heaps, advanced (if minimal latency is needed).
- General concepts: Stop-the-World, safepoints, card table, write barriers.
Typical errors
- Small heap ⇒ frequent GC pauses, OutOfMemoryError.
- Leaks through static collections/caches or listeners (refs are not released).
- Does not consider off-heap (DirectByteBuffer, native driver memory) and blames the heap.
Best practices for memory
- Start with G1, ensure the heap size is reasonable (minimum = maximum for stability).
- Monitor metaspace and direct memory.
- Profile allocations; reduce short-lived objects in hot code.
- For caches — soft/weak references as needed, limit size.
Diagnostic Tools
1. jconsole (simple telemetry via JMX)
- Launch:
jconsole→ select process (local) or connect via JMX. - What to look for:
- Memory: heap/metaspace graphs, Perform GC button (for diagnostics only).
- Threads: thread states, Detect Deadlock.
- MBeans: application/pool parameters (if exposed).
- When useful: first approximation – see heap growth, GC count, deadlock in one click.
2. jvisualvm (GUI profiler/dump analysis)
- Launch:
jvisualvm(or VisualVM as a separate installation). - Features:
- Sampler CPU/Memory (less overhead) and Profiler (more accurate, but heavier).
- Open heap dump (
*.hprof), view Top Consumers (who eats memory), inspect reachability graph. - Plugins: VisualGC (heap generation graph).
- When useful: find leaks, hot methods, confirm hypotheses before/after fixes.
3. jmap (heap snapshot and histogram)
Основные команды:
jmap -histo <PID> # top classes by count/size
jmap -histo:live <PID> # only live objects (after GC)
jmap -dump:format=b,file=heap.hprof <PID> # heap dump- Usage: see what the heap is filled with now; download dump and open in VisualVM/YourKit/JProfiler.
4. jstack (thread dump)
jstack -l <PID> # thread dump with monitors
jstack -m <PID> # with native code stackWhat to look for:
- Threads in BLOCKED (monitor/lock), WAITING/TIMED_WAITING (waiting), RUNNABLE (CPU).
- Cyclic dependencies (deadlock): usually a section “Found one Java-level deadlock” at the end of the dump.
- “Hot thread”: thread that is constantly in RUNNABLE with the same stack trace.
Useful addition (not requested, but must-have):
jcmd <PID> VM.native_memory summary– native/metaspace summary;jcmd <PID> GC.heap_info | GC.class_histogram | Thread.print.
Mini-Cases (code → how to diagnose)
1. Memory leak through a static list
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);
}
}Diagnosis:
jconsole→ Memory grows incrementally, GC doesn’t help → suspicion of retention.jmap -histo:live <PID>→ manybyte[].jmap -dump:format=b,file=heap.hprof <PID>→ open in VisualVM, find GC Roots → see static fieldLeak.bag. Fix: limit/clean the cache, use WeakReference/SoftReference if necessary.
2. Deadlock due to lock order
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) {} } }
}Diagnosis:
jconsole→ Threads → Detect Deadlock.jstack -l <PID>→ section “Found one Java-level deadlock” with list of threads and monitor owners. Fix: strict lock order (alwaysathenb), ortryLock/timeouts, or larger lock granularity.
3. High CPU on one thread
Symptom: 100% cores. Diagnosis:
top/htop→ PID → tid of thread with high CPU.jstack <PID>several times in a row → the same stack trace in RUNNABLE ⇒ hot spot.- Then jvisualvm CPU Sampler/Profiler for confirmation.
Check-list for problem searching (quick)
- Memory:
jconsolegraphs →jmap -histo:live→ heap dump → VisualVM. - Pauses/GC: enable GC logs (
-Xlog:gc*in 17+), check collector/heap size. - CPU:
jstackseveral times → hot method →jvisualvmSampler. - Locks:
jconsoleDetect Deadlock →jstack -l. - Native/Metaspace:
jcmd VM.native_memory summary,-XX:MaxMetaspaceSize.
Best practices (summary)
- For production: G1, adequate
-Xms = -Xmx, GC logs (-Xlog:gc*:file=gc.log). - For analysis: VisualVM/
jconsolefor initial symptoms, jmap/jstack for facts. - Reduce allocation in hot spots, avoid unnecessary boxing and temporary objects.
- Design locks: order, timeouts,
Lock/StampedLockfor read access. - Cover microbenchmarks with JMH, not custom timers.
- Monitor Metaspace/Code Cache/Direct memory – these are also causes of OOME.
- For caches — limits and weak/soft references where appropriate.