JVM & Memory
JVM/JRE/JDK, platform independence, bytecode execution, memory areas, and garbage collection.
9 questions
MiddleTheoryVery commonWhat is the difference between the heap and the stack in the JVM?
What is the difference between the heap and the stack in the JVM?
The heap holds objects created with new, is shared across all threads, and is cleaned up by garbage collection. The stack holds method frames — each frame's local variables and operands — is private to one thread, and works LIFO. A frame is pushed on a call and popped when the method returns, freeing its locals automatically with no GC involved.
Common mistakes
- ✗Swapping their roles — saying objects live on the
stackand frames on theheap - ✗Claiming the
stackis garbage-collected, when frames are freed by simple pop on return - ✗Forgetting the
stackis per-thread while theheapis shared across threads
Follow-up questions
- →Where does a local variable that references a
heapobject actually store that reference? - →Why does deep recursion throw
StackOverflowErrorrather thanOutOfMemoryError?
MiddleTheoryVery commonWhat are the main runtime memory areas of the JVM?
What are the main runtime memory areas of the JVM?
The heap is shared across all threads and holds every object. Each thread gets its own stack of frames holding locals and partial results. The method area (metaspace) stores class metadata, static fields, and the runtime constant pool. Per-thread PC registers track the current instruction, and native method stacks back calls into non-Java code. Heap and method area are shared; stacks and PC registers are per-thread.
Common mistakes
- ✗Mixing up which areas are thread-shared (
heap, method area) versus per-thread (stacks) - ✗Forgetting the method area/metaspace exists and putting class metadata on the
heapwith objects - ✗Assuming objects can live on the
stackrather than always on theheap
Follow-up questions
- →Why was the permanent generation replaced by metaspace, and where does metaspace live?
- →Which area throws
StackOverflowErrorversusOutOfMemoryError, and why?
JuniorTheoryCommonWhat is the difference between the JVM, the JRE, and the JDK?
What is the difference between the JVM, the JRE, and the JDK?
The JVM is the engine that loads and runs Java bytecode on a given platform. The JRE bundles the JVM plus the standard libraries needed to run an application. The JDK is the JRE plus development tools — the javac compiler, debugger, and jar — needed to build one. They nest: JDK ⊃ JRE ⊃ JVM.
Common mistakes
- ✗Thinking the JRE includes
javac, when the compiler ships only in the JDK - ✗Believing you need the full JDK just to run an already-compiled application
- ✗Confusing the JVM (the execution engine) with the whole JDK toolkit
Follow-up questions
- →Since modern JDKs bundle everything, why distinguish a separate JRE at all?
- →What does the JVM's just-in-time (
JIT) compiler add beyond plain execution?
MiddleTheoryCommonHow does the JVM execute a compiled class, step by step?
How does the JVM execute a compiled class, step by step?
The classloader loads the .class file into memory. The bytecode verifier then checks it is well-formed and type-safe. The execution engine runs it: it interprets bytecode instruction by instruction, while the just-in-time (JIT) compiler watches for hot methods and compiles those to native code for speed. So execution is hybrid — interpreted first, then JIT-optimized where it pays off.
Common mistakes
- ✗Thinking the JVM compiles the whole class to native code up front like a C compiler
- ✗Believing the
JITis just an interpreter rather than a native-code compiler for hot methods - ✗Skipping the verification step, assuming any
.classis trusted to be type-safe
Follow-up questions
- →How does the JVM decide a method is "hot" enough to
JIT-compile? - →What kinds of malformed
bytecodedoes the verifier reject before execution?
JuniorTheoryOccasionalHow does Java achieve "write once, run anywhere" platform independence?
How does Java achieve "write once, run anywhere" platform independence?
javac compiles source into platform-neutral bytecode rather than native machine code. That bytecode is the same on every operating system. To run it, each platform ships its own JVM that reads the portable bytecode and executes it for that hardware. So the compiled artifact is portable; the JVM beneath it is platform-specific.
Common mistakes
- ✗Calling the compiled
bytecodenative machine code instead of a portable intermediate form - ✗Assuming the JVM itself is identical and portable across platforms
- ✗Thinking the source
.javafiles, not thebytecode, are what runs everywhere
Follow-up questions
- →If
bytecodeis portable, why must a JVM be downloaded separately per operating system? - →How does the just-in-time (
JIT) compiler reconcile portability with native-speed execution?
MiddleTheoryOccasionalWhat are the bootstrap, platform, and application class loaders, and how do they cooperate?
What are the bootstrap, platform, and application class loaders, and how do they cooperate?
They are the JVM's three built-in class loaders, arranged as a parent chain. The bootstrap loader is native and loads the core JDK classes (java.base); the platform loader loads the rest of the JDK modules; the application loader loads your own classes from the classpath. They follow parent delegation: a loader first asks its parent and defines the class itself only if the parent cannot find it. That is what stops a classpath java.lang.String from ever displacing the real one.
Common mistakes
- ✗Thinking the application loader is consulted before its parents
- ✗Believing a class's identity is its name alone, ignoring the loader that defined it
- ✗Assuming every class is loaded eagerly at startup rather than on first use
Follow-up questions
- →Why can two classes with the same fully-qualified name be different types at run time?
- →What did the platform class loader replace when the JDK became modular?
MiddleTheoryOccasionalHow does metaspace differ from the PermGen it replaced in Java 8?
How does metaspace differ from the PermGen it replaced in Java 8?
PermGen was a fixed-size region inside the heap that held class metadata; when it filled you got OutOfMemoryError: PermGen space and had to tune -XX:MaxPermSize. Java 8 replaced it with metaspace, which lives in native memory outside the heap and grows on demand, capped only by -XX:MaxMetaspaceSize (unlimited by default). Class metadata is freed when its class loader is unloaded, so a class-loading leak now shows up as native growth, not heap growth.
Common mistakes
- ✗Thinking metaspace is a heap region bounded by
-Xmx - ✗Expecting metaspace to be fixed-size by default the way PermGen was
- ✗Believing class metadata is never reclaimed once the class has been loaded
Follow-up questions
- →What does a steadily growing metaspace usually tell you about the application's class loaders?
- →Why does
-XX:MaxMetaspaceSizestill matter if metaspace grows on demand?
MiddleTheoryRareWhat does the Java Platform Module System JPMS solve that the classpath could not?
What does the Java Platform Module System JPMS solve that the classpath could not?
A module-info.java names the module, what it requires, and which packages it exports. That buys strong encapsulation — a non-exported package is unreachable, even by reflection — and reliable configuration: a missing or duplicate module fails at startup, not as a NoClassDefFoundError mid-run. The classpath was a flat, order-dependent bag of jars with no public API. The module path reads jars as modules; code left on the classpath falls into the unnamed module and keeps the old behaviour.
Common mistakes
- ✗Treating
module-info.javaas a dependency manifest like a Mavenpom.xml - ✗Expecting reflection to reach a package the module does not
export - ✗Thinking the classpath is gone rather than surviving as the unnamed module
Follow-up questions
- →What is the unnamed module, and what does a jar left on the classpath get from it?
- →How does
opensdiffer fromexportswhen a framework needs reflective access?
SeniorDesignRareYou are baselining the JDK for a new backend service that will live for years, and the decision has to be made before the first release. The rest of the platform runs Java 17; the platform team supports any long-term-support line. The service is I/O-heavy — thousands of concurrent requests fanning out to downstream HTTP APIs — and the team plans to lean on virtual threads and Stream pipelines. Java 21 has been the default since it shipped; Java 25 is the newest LTS line. Two engineers want to hold at 21, arguing that the real prize is structured concurrency — cancelling a whole fan-out as one unit — and that it is worth waiting for. Which line would you baseline on, and why? Say what Java 25 actually finalizes over 21, what is still preview there, and what would keep you on 21 instead.
You are baselining the JDK for a new backend service that will live for years, and the decision has to be made before the first release. The rest of the platform runs Java 17; the platform team supports any long-term-support line. The service is I/O-heavy — thousands of concurrent requests fanning out to downstream HTTP APIs — and the team plans to lean on virtual threads and Stream pipelines. Java 21 has been the default since it shipped; Java 25 is the newest LTS line. Two engineers want to hold at 21, arguing that the real prize is structured concurrency — cancelling a whole fan-out as one unit — and that it is worth waiting for. Which line would you baseline on, and why? Say what Java 25 actually finalizes over 21, what is still preview there, and what would keep you on 21 instead.
Baseline on Java 25: it is the current LTS line, so nothing 21 gives you is lost. Over 21 it finalizes Scoped Values (JEP 506) and inherits from Java 24 the virtual-thread pinning fix (JEP 491 — synchronized no longer pins a carrier thread) and final Stream Gatherers (JEP 485), all of which this I/O-heavy service leans on. Structured concurrency is still preview in 25, so it is no reason to jump. Stay on 21 only if a critical dependency, agent, or vendor JDK is not yet certified for 25.
Common mistakes
- ✗Treating Java 25 as a short-lived feature release rather than the current LTS line
- ✗Believing structured concurrency is final in 25 and planning the design around it
- ✗Assuming virtual threads behave identically on 21 and 25, pinning included
Follow-up questions
- →Why does the
synchronizedpinning fix change how much of your code can safely run on virtual threads? - →What do preview APIs like structured concurrency cost you in build and runtime flags?