JVM Interview Preparation
A. JVM Basics JVM (Java Virtual Machine) is a virtual runtime environment that executes Java bytecode. It acts as a bridge between Java programs and the underlying operatingβ¦
A. JVM Basics
Expand a question to read the answer. Tick when youβre prepared (progress saved on this device).
JVM (Java Virtual Machine) is a virtual runtime environment that executes Java bytecode. It acts as a bridge between Java programs and the underlying operating system, making Java platform-independent.
The JVM is responsible for:
- Loading class files
- Verifying bytecode
- Managing memory
- Executing code
- Performing Garbage Collection (GC)
- Handling multithreading
- Providing security
Interview One-liner: JVM is the engine that converts Java bytecode into machine code and executes it while managing memory, security, and garbage collection.
| Feature | JVM | JRE | JDK |
|---|---|---|---|
| Full Form | Java Virtual Machine | Java Runtime Environment | Java Development Kit |
| Purpose | Executes bytecode | Runs Java applications | Develop + Run Java applications |
| Contains | JVM only | JVM + Libraries | JRE + Compiler + Development Tools |
| Compiler | β | β | β javac |
| Used By | Runtime | End Users | Developers |
Relationship
JDK βββ JRE β βββ JVM β βββ Java Libraries βββ javac βββ javadoc βββ jdb βββ Other Development Tools
Interview Tip
- JVM β Executes
- JRE β Runs
- JDK β Develops
Step 1 Write Java Source Code β Employee.java Step 2 Compile javac Employee.java β Step 3 Bytecode Generated Employee.class β Step 4 JVM loads .class file β Step 5 Class Loader loads classes β Step 6 Bytecode Verifier checks security β Step 7 Execution Engine β Interpreter + JIT Compiler β Machine Code β CPU Executes
Interview One-liner: Java source code is compiled into platform-independent bytecode. The JVM loads, verifies, and executes this bytecode by converting it into machine code using the Interpreter and JIT Compiler.
Suppose we execute:
- java Employee
Internally:
1. JVM Starts
- JVM process is created.
2. Class Loader Loads Employee.class
- Bootstrap
- Platform
- Application ClassLoader
3. Bytecode Verification
Checks:
- Illegal code
- Stack integrity
- Memory safety
- Type safety
4. Linking
- Verification
- Preparation
- Resolution
5. Initialization
- Static variables and static blocks execute.
- static {
- System.out.println(“Loaded”);
- }
6. Main Thread Starts
- public static void main(String[] args)
7. Stack Frame Created
Contains:
- Local variables
- Parameters
- Operand stack
8. Object Creation
- Employee e = new Employee();
- Memory allocated in Heap.
9. Execution Engine Executes Bytecode
Initially:
- Interpreter executes instructions.
Frequently executed code:
- JIT compiles into native machine code.
10. Garbage Collector Frees Unused Objects
- Unused heap memory is reclaimed automatically.
| Interpreter | JIT Compiler |
|---|---|
| Executes line by line | Compiles whole method |
| Slower | Faster |
| No optimization | Heavy optimization |
| Starts quickly | Takes compilation time |
| Used for rarely executed code | Used for frequently executed (“hot”) code |
The JVM initially interprets bytecode. When a method is executed many times, the JIT compiler compiles it into optimized native machine code, improving performance.
HotSpot is Oracle’s JVM implementation.
It improves performance by identifying frequently executed (“hot”) code and compiling it into optimized machine code using the JIT compiler.
Features:
- JIT Compilation
- Adaptive Optimization
- Efficient Garbage Collection
- Escape Analysis
- Inlining
- Dead Code Elimination
- Why “HotSpot”?
- Frequently executed methods are called Hot Spots.
- Instead of interpreting them repeatedly, HotSpot compiles them once into machine code.
B. Class Loading
Expand a question to read the answer. Tick when youβre prepared (progress saved on this device).
A ClassLoader is a JVM component responsible for loading .class files into memory when they are first needed.
Responsibilities:
- Load class bytecode
- Define the class in the JVM
- Support dynamic loading
- Avoid duplicate loading
Interview One-liner: ClassLoader loads Java classes into JVM memory on demand.
There are three built-in ClassLoaders.
1. Bootstrap ClassLoader
- Loads core Java classes.
Examples
- java.lang.String
- java.util.List
- java.lang.Object
Loads from:
- JAVA_HOME/lib
- Implemented in native code (not Java).
2. Platform ClassLoader
- Loads platform modules.
Examples
- java.sql
- java.xml
- jdk.crypto
3. Application (System) ClassLoader
- Loads application classes.
Example
Employee.class
Student.class Loads from: classpath Hierarchy Bootstrap β Platform β Application
When a class is requested:
- Application Loader
β
βΌ
Platform Loader
β
βΌ
Bootstrap Loader
The child first asks its parent to load the class.
If the parent cannot load it, the child attempts to load it.
Why?
- Security
- Prevent duplicate loading
- Ensure core Java classes are loaded only by the Bootstrap ClassLoader
Loading β Linking β Initialization
1. Loading
- Read .class file.
- Create Class object.
2. Linking
- Verification
- Check bytecode validity.
- Preparation
- Allocate memory for static variables.
- Assign default values.
- Resolution
- Convert symbolic references to direct references.
3. Initialization
Execute:
- Static variable assignments
- Static blocks
Example
static int x = 10;
static {
System.out.println("Loaded");
}
loadClass()
Class.forName()
Loads class only
Loads and initializes class
Static block not executed
Static block executed
Used by ClassLoader
Used for reflection, JDBC
Example
ClassLoader.getSystemClassLoader().loadClass("Employee");
Static block won't execute.
Class.forName("Employee");
Static block executes immediately.
Yes.
The JVM considers a class unique by the combination of:
- Fully qualified class name
- ClassLoader that loaded it
- So, two different ClassLoaders can load the same class name, and the JVM treats them as different classes.
This is commonly used in:
- Application servers
- OSGi
- Plugin architectures
C. JVM Memory
Expand a question to read the answer. Tick when youβre prepared (progress saved on this device).
JVM Memory
+———————–+
| Heap Memory |
+———————–+
+———————–+
| Metaspace |
+———————–+
Thread 1
+———–+
| PC Register|
+———–+
| Java Stack |
+———–+
Thread 2
+———–+
| PC Register|
+———–+
| Java Stack |
+———–+
+———————–+
| Native Method Stack |
+———————–+
Heap
- Stores objects and arrays.
- Shared among all threads.
- Managed by the Garbage Collector.
- Stack
- One stack per thread.
- Stores method calls, local variables, and references.
- Metaspace
- Stores class metadata.
- Uses native memory.
- PC Register
- Stores the current instruction address for each thread.
- Native Method Stack
- Used for JNI (Java Native Interface) calls.
| Heap | Stack |
|---|---|
| Stores objects | Stores local variables and method frames |
| Shared | One per thread |
| Garbage collected | Automatically freed when method exits |
| Larger memory | Smaller memory |
| Slower access | Faster access |
Example
Employee e = new Employee();
Heap:
- Employee Object
Stack:
- e β reference
| PermGen | Metaspace |
|---|---|
| Java 7 and earlier | Java 8+ |
| Part of JVM heap | Native memory |
| Fixed size | Can grow dynamically |
| Frequent OutOfMemoryError | Less likely to overflow |
PermGen stored class metadata before Java 8. It was replaced by Metaspace, which stores metadata in native memory and grows dynamically, reducing metadata-related memory issues.
Item
Memory Area
Objects
Heap
Local Variables
Stack
Method Parameters
Stack
Object References
Stack
Static Variables
Heap (associated with the class)
Class Metadata
Metaspace
String Literals
String Pool (inside Heap)
Interned Strings
String Pool (Heap)
Example
class Employee {
static int companyId = 10;
public static void main(String[] args) {
int age = 25;
Employee e = new Employee();
String s = "Java";
}
}
Memory allocation:
- companyId β Heap (static field associated with the loaded class)
- age β Stack
- e (reference) β Stack
- Employee object β Heap
- “Java” β String Pool (Heap)
| StackOverflowError | OutOfMemoryError |
|---|---|
| Stack is full | Heap/Metaspace/Native memory exhausted |
| Usually due to infinite recursion | Usually due to memory leaks or excessive allocation |
| Happens per thread | Can affect the entire JVM |
StackOverflowError Example
public class Test {
static void demo() {
demo();
}
public static void main(String[] args) {
demo();
}
}
Each recursive call creates a new stack frame until the thread’s stack is exhausted.
OutOfMemoryError Example
import java.util.*;
public class Test {
public static void main(String[] args) {
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]); // Allocate 1 MB repeatedly
}
}
}
The heap keeps growing with objects that remain reachable, eventually exhausting available memory.
Interview Tip
- StackOverflowError = Too many method calls (deep/infinite recursion).
- OutOfMemoryError = Not enough memory to allocate more objects or metadata.
D. Heap & Garbage Collection
Expand a question to read the answer. Tick when youβre prepared (progress saved on this device).
The Heap is the memory area where all Java objects are allocated. It is shared among all threads and managed by the Garbage Collector.
The Heap is divided into three main generations:
- Heap Memory
- +———————-+
- | Old Generation |
- +———————-+
- +———-+———–+
- | Survivor | Survivor |
- | S0 | S1 |
- +———-+———–+
- +———————-+
- | Eden Space |
- +———————-+
1. Eden Space
- New objects are created here.
- Most objects die here.
Example
Employee e = new Employee();
Initially stored in Eden.
2. Survivor Spaces (S0 & S1)
- Objects that survive a Minor GC move from Eden to a Survivor space.
Example
Eden β Survivor S0 β Survivor S1 β Old Generation
Objects keep aging with every GC cycle.
3. Old Generation (Tenured)
- Objects that survive multiple Minor GCs are promoted to the Old Generation.
Examples
- Spring Singleton Beans
- Cache objects
- Long-lived Sessions
Interview Tip: Most objects die young, which is why Java separates the Heap into Young and Old generations to optimize garbage collection.
| Minor GC | Major GC |
|---|---|
| Full GC | Cleans Young Generation |
| Cleans Old Generation | Cleans entire Heap + Metaspace (when needed) |
| Fast | Slower |
| Slowest | Frequent |
| Less frequent | Rare |
| Minor GC cleans Eden and Survivor spaces. | Major GC cleans the Old Generation. |
- Full GC cleans the entire heap and may also reclaim class metadata when applicable. It pauses all application threads and is the most expensive.
Garbage Collection automatically removes objects that are no longer reachable.
Example
Employee e = new Employee();
e = null;
Now no reference points to the object.
GC Steps
Object Created β Application Uses Object β Reference Removed β GC Finds Unreachable Object β Memory Reclaimed
The JVM decides when to run GC based on memory pressureβit is not scheduled by the programmer.
Modern JVMs determine whether an object is alive using Reachability Analysis.
GC starts from GC Roots and traverses all reachable objects.
GC Root
|
v
Object A
|
v
Object B
If an object cannot be reached from any GC Root, it becomes eligible for collection.
Common GC Roots
- Local variables in Stack
- Active Threads
- Static Variables
- JNI References
Reference Counting cannot collect cyclic references.
Example
class A {
B b;
}
class B {
A a;
}
A —–> B
^ |
|_________|
Even if nothing else references them, each object still references the other.
Reference count never becomes zero.
Reachability Analysis correctly identifies this cycle as unreachable and collects both objects.
Interview Tip: Java uses Reachability Analysis because it can collect cyclic objects, unlike Reference Counting.
Reference
GC Behavior
Typical Use
Strong
Never collected while referenced
Normal objects
Soft
Collected only when memory is low
Memory-sensitive caches
Weak
Collected in the next GC cycle
WeakHashMap, canonical mappings
Phantom
Collected after finalization; cannot access object
Cleanup after object reclamation
Strong Reference
Employee e = new Employee();
Normal reference.
Soft Reference
SoftReference<Employee> ref =
new SoftReference<>(new Employee());
Good for caches.
Weak Reference
WeakReference<Employee> ref =
new WeakReference<>(new Employee());
Collected during the next GC if no strong reference exists.
Phantom Reference
Used together with a ReferenceQueue for advanced cleanup after the object has become unreachable.
finalize() is deprecated because it is:
- Unpredictable (no guarantee when or if it runs)
- Slow
- Can delay memory reclamation
- Difficult to implement correctly
Modern alternatives:
- try-with-resources
- AutoCloseable
- Cleaner API (where appropriate)
Interview Tip: finalize() should not be used for releasing resources like files or database connections.
No.
System.gc();
only requests garbage collection.
The JVM may ignore the request.
Interview Statement: System.gc() is only a suggestion to the JVM, not a guarantee that GC will run.
E. Garbage Collectors
Expand a question to read the answer. Tick when youβre prepared (progress saved on this device).
- Single GC thread
- Stops all application threads
- Best for small applications
JVM Option
-XX:+UseSerialGC
Pros
- Simple
- Low memory overhead
Cons
- Poor scalability
Uses multiple threads for GC to improve throughput.
-XX:+UseParallelGC
Pros
- High throughput
- Good for CPU-intensive workloads
Cons
- Longer pause times
G1 (Garbage-First) divides the heap into many small regions instead of fixed generations.
It prioritizes collecting regions with the most reclaimable garbage.
Pros
- Predictable pause times
- Good for large heaps
- Balances throughput and latency
-XX:+UseG1GC
| CMS | G1 |
|---|---|
| Deprecated | Current recommended collector for many server workloads |
| Fragmentation issues | Compacts memory |
| Lower pause times | Predictable pause targets |
| Old-generation focused | Entire heap managed by regions |
G1 replaced CMS because it reduces fragmentation and provides more predictable pause times while handling large heaps efficiently.
For modern JDKs (Java 9 and later, including Java 17 and Java 21), G1 GC is the default garbage collector.
| Throughput | Latency |
|---|---|
| Amount of work completed | Time taken to respond |
| Higher throughput often allows longer pauses | Lower latency minimizes pause times |
Interview Tip
- Batch processing β Throughput
- Web applications β Latency
F. String Pool
Expand a question to read the answer. Tick when youβre prepared (progress saved on this device).
The String Constant Pool is a special area in the Heap where string literals are stored.
Example
String s1 = "Java";
String s2 = "Java";
Both variables reference the same object.
s1 —-
—> “Java”
/
s2 —-/
This saves memory by reusing immutable strings.
String s1 = "Java";
String s2 = new String("Java");
| String Literal | new String() |
|---|---|
| Uses String Pool | Creates a new heap object |
| Reuses existing literal | Always creates a new object |
| More memory efficient | Extra memory allocation |
intern() returns the pooled version of a string.
String s = new String("Java");
String p = s.intern();
If "Java" already exists in the pool, p references that pooled object.
String is immutable because it improves:
- Security
- Thread safety
- String Pool sharing
- Hash code caching
- Performance
Example
String s = "Java";
s.concat("8");
The original string remains unchanged.
G. JVM Performance
Expand a question to read the answer. Tick when youβre prepared (progress saved on this device).
| Option | Meaning |
|---|---|
| -Xms | Initial heap size |
| -Xmx | Maximum heap size |
Example
-Xms512m -Xmx2g
The JVM starts with 512 MB of heap and can grow up to 2 GB.
| Heap Dump | Thread Dump |
|---|---|
| Snapshot of memory | Snapshot of threads |
| Used for memory leaks | Used for deadlocks, hangs, high CPU |
| Contains objects | Contains thread stack traces |
Tool
Purpose
jstack
Capture thread dump
jmap
Heap dump and memory information
jcmd
General JVM diagnostics and management
JFR (Java Flight Recorder)
Low-overhead performance profiling and event recording
Interview Tip
- High CPU β jstack
- Memory leak β jmap
- JVM diagnostics β jcmd
- Production profiling β JFR
Typical steps
- Observe continuously increasing heap usage.
- Capture a heap dump.
- Analyze it using tools like Eclipse MAT or VisualVM.
- Find objects with unexpectedly high retained memory.
- Trace why they are still reachable (e.g., static collections, caches, listeners).
Common causes
- Static collections
- Uncleared caches
- Listener leaks
- ThreadLocal misuse
H. Production Scenario Questions
Expand a question to read the answer. Tick when youβre prepared (progress saved on this device).
Java Source (.java) β javac Compiler β Bytecode (.class) β ClassLoader β Bytecode Verifier β Linking β Initialization β Execution Engine (Interpreter + JIT) β Machine Code β CPU Executes
During execution
- Objects are created in the Heap.
- Method calls use the Stack.
- GC automatically reclaims unreachable objects.
class Employee {
static int companyId = 100;
public static void main(String[] args) {
int age = 25;
Employee e = new Employee();
String name = "John";
}
}
Item
Memory Area
companyId
Heap (static field associated with the class)
age
Stack
e (reference)
Stack
Employee object
Heap
“John”
String Pool (Heap)
Class metadata
Metaspace
Objects can:
- Be shared by multiple methods and threads.
- Outlive the method that created them.
- Have variable sizes.
- The Stack is designed for short-lived method execution and is automatically cleared when a method returns.
Each thread needs an independent execution context containing:
- Local variables
- Parameters
- Method call history
- Operand stack
- This isolation avoids interference between threads and improves performance.
void demo() {
demo();
}
Each recursive call creates a new stack frame.
Eventually the thread stack becomes full, causing:
- java.lang.StackOverflowError
List<byte[]> list = new ArrayList<>();
while (true) {
list.add(new byte[1024 * 1024]);
}
The list keeps strong references to every allocated array, preventing garbage collection.
Eventually the heap is exhausted and the JVM throws:
- java.lang.OutOfMemoryError: Java heap space
- Check heap usage using monitoring tools (Prometheus, Grafana, JMX, etc.).
- Review GC logs and GC frequency.
- Capture a heap dump (jcmd or jmap).
- Analyze the dump using Eclipse MAT or VisualVM.
- Identify large objects and retained memory.
- Check caches, static collections, ThreadLocals, and connection/resource handling.
- Fix the root cause and verify with load testing.
Interview Tip: Don’t restart the JVM immediately unless necessaryβcollect diagnostic data first.
Signs:
- Heap usage keeps growing after Full GC.
- Frequent OutOfMemoryError.
- Increasing GC pauses.
Investigation:
- Capture multiple heap dumps over time.
- Compare retained objects.
- Identify objects that should have been released but remain reachable.
- Find the reference chain keeping them alive.
I would check:
- Heap sizing (-Xms, -Xmx)
- GC logs for pause time and frequency
- Memory leaks
- Large caches or static collections
- Excessive object creation
- Long-lived objects filling the Old Generation
- GC configuration and collector selection
- Application traffic changes or unusual workload
For a typical Spring Boot microservice running on Java 17 or Java 21, I would generally choose G1 GC because:
- It is the default collector.
- It provides predictable pause times.
- It balances throughput and latency well.
- It performs efficiently on medium to large heaps.
- It is well suited for long-running server applications.
If the application had extremely low-latency requirements, I would also evaluate collectors such as ZGC or Shenandoah (where available and appropriate), but for most production Spring Boot services, G1 GC is a strong default choice.
Interview One-liner: For most Spring Boot microservices, I would use G1 GC because it offers a good balance of throughput, predictable pause times, and scalability, making it suitable for long-running production services.