HomeBytesheetsRoadmapsUI TemplatesUI ToolsStack Discussion
4 Topics
Chapter•85 Minutes

JVM, Class Loading, and Runtime Memory

Go beneath the language into class loading, bytecode, runtime data areas, stack frames, heap, metaspace, JIT compilation, and JVM diagnostics.

Class Loading, Linking, and Initialization

Before a class can be used, the JVM loads its class representation, verifies and prepares it, resolves symbolic references as required, and initializes static state when initialization is triggered.

Class loaders form a delegation hierarchy. Loader identity is part of a class's runtime identity, so the same binary name loaded by different defining loaders represents distinct runtime types.

Class<?> type = Class.forName("java.lang.String");
System.out.println(type.getClassLoader());

This enables plugins and isolation while also creating class-loader leak risks when long-lived references retain unloadable class loaders.

Key Takeaways
  • Loading, linking, and initialization are distinct concepts.
  • Class identity includes the defining class loader.
  • Custom class loaders are powerful but add lifecycle complexity.

JVM Runtime Data Areas

The JVM specification describes runtime data areas such as the heap, JVM stacks, method area, program counter registers, and native method stacks.

The heap is shared among threads and stores objects and arrays. Each thread has its own JVM stack containing frames for active method invocations.

static int calculate(int x) {
    int doubled = x * 2;
    return doubled + 1;
}

Each call creates a frame on the executing thread's stack. Object values created by application code are generally managed on the heap.

Simplified JVM Runtime Areas
Key Takeaways
  • The heap is shared; each thread has its own JVM stack.
  • Stack frames represent active method invocations.
  • JVM specification concepts and HotSpot implementation details should not be conflated.

Interpreter, JIT, and Hot Code

The JVM can interpret bytecode and compile frequently executed code to native machine instructions using JIT compilation. Runtime profiling allows optimizations such as inlining, dead-code elimination, escape analysis, and speculative optimization.

Bytecode
   |
   +--> Interpreter --> execution
   |
   +--> JIT compiler --> native code --> optimized hot path
                              |
                              +--> deoptimization if assumptions fail

This means startup and steady-state performance can differ. Benchmarking Java requires warm-up, realistic workloads, and proper measurement.

Benchmarking

Do not conclude that one Java implementation is faster from a tiny cold-start benchmark; JIT warm-up matters.

Key Takeaways
  • JIT compilation is profile-guided and runtime-aware.
  • Optimized code can be deoptimized when assumptions fail.
  • Use proper benchmarking methodology.

JVM Diagnostics and Troubleshooting

Production debugging should start with evidence. Useful tools include jcmd, jstack, jmap, Java Flight Recorder, Java Mission Control, and JVM logging facilities.

jcmd <pid> VM.flags
jcmd <pid> GC.heap_info
jcmd <pid> Thread.print
jcmd <pid> JFR.start duration=60s filename=recording.jfr

Thread dumps help identify deadlocks and blocked threads. Heap information helps investigate memory pressure. Flight Recorder captures runtime events for CPU, allocation, locks, I/O, and GC analysis.

The workflow is: observe, collect evidence, form a hypothesis, make a targeted change, and verify.

Key Takeaways
  • Use thread dumps for thread-state problems.
  • Use heap/GC evidence for memory problems.
  • Java Flight Recorder is valuable for correlating CPU, allocation, locks, and GC behavior.