Java Interview Questions & Answers: Part 4
This guide covers software design patterns, SOLID principles, garbage collection mechanics, and JVM log diagnostics.
Object-Oriented Design Principles and Patterns
1. What is the Decorator pattern? Give a real-world example.
The Decorator pattern dynamically attaches new behaviors or responsibilities to an object at runtime without altering its structural classes. It uses composition instead of inheritance to extend functionality.
JDK Example: Java I/O Streamsโ
The java.io framework is built heavily on decorators:
// FileReader: The Concrete Component (reads bytes)
FileReader fileReader = new FileReader("config.txt");
// BufferedReader: The Decorator (adds buffering capacity to the reader)
BufferedReader bufferedReader = new BufferedReader(fileReader);
Code Implementation Patternโ
interface Coffee { double getCost(); }
class SimpleCoffee implements Coffee { public double getCost() { return 2.0; } }
abstract class CoffeeDecorator implements Coffee {
protected final Coffee decoratedCoffee;
protected CoffeeDecorator(Coffee coffee) { this.decoratedCoffee = coffee; }
public double getCost() { return decoratedCoffee.getCost(); }
}
class MilkDecorator extends CoffeeDecorator {
public MilkDecorator(Coffee coffee) { super(coffee); }
@Override
public double getCost() { return super.getCost() + 0.5; } // Dynamically add cost
}
2. What is the difference between Decorator and Proxy Pattern?
Although both patterns share the identical structural design (implementing the interface of the wrapped class), their intents differ:
- Decorator Pattern: Extends the responsibilities of the object. The client directly instantiates the object and then dynamically wraps it in decorators to add functionality.
- Proxy Pattern: Controls or restricts access to the underlying object. The client typically interacts only with the proxy. The proxy manages the lifecycle of the real subject internally (e.g., lazy initialization, security check, logging, remote network invocation).
3. SOLID Design Principles Deep Dive
Liskov Substitution Principle (LSP)โ
Objects of a superclass should be replaceable with objects of its subclasses without breaking the correctness of the application.
// VIOLATION: Square inherits Rectangle but violates the invariant that width != height
class Rectangle {
protected int width, height;
public void setWidth(int w) { this.width = w; }
public void setHeight(int h) { this.height = h; }
}
class Square extends Rectangle {
@Override
public void setWidth(int w) { this.width = w; this.height = w; }
@Override
public void setHeight(int h) { this.width = h; this.height = h; }
}
// An assertion expecting setWidth to not alter height will fail when passing a Square.
Correction: Model them separately or extract a common interface/abstract class that does not enforce rectangle-specific dimensions.
Garbage Collection (GC) and JVM Internals
4. How do JVM Garbage Collectors work?
Modern JVMs use Generational Garbage Collection based on the empirical observation that most objects die young. The heap is split into:
| Generation Partition | Sub-Region Spaces | Purpose & Allocation Model | Promotion & Tenuring Dynamics |
|---|---|---|---|
| Young Generation | Eden Space (allocations) S0 (From) active survivor S1 (To) empty survivor | Receives all newly instantiated objects via new. Low survival rate (~90% die in Eden). | Surviving objects copied between S0/S1; tenuring age increments with each Minor GC cycle. |
| Old (Tenured) Generation | Single continuous or region-based pool | Houses persistent domain entities, singleton services, long-lived caches. | Promoted after surviving 15 Minor GC cycles (-XX:MaxTenuringThreshold=15). Major/Mixed GC collects. |
The Minor GC Promotion Flowโ
- Allocation: All new objects are allocated in the Eden space.
- First Minor GC: When Eden is full, a Minor GC triggers. The JVM halts application threads (STW pause). Live objects in Eden are moved to S0 (Survivor space), and their age is set to 1. Eden is cleared.
- Subsequent GC Cycles: In the next Minor GC, live objects from Eden and S0 are copied to S1. S0 is cleared. The survivor spaces swap roles. The age of surviving objects increments.
- Promotion: When an object survives the threshold age (configured by
-XX:MaxTenuringThreshold=15), it is promoted to the Old Generation.
5. G1 GC vs. ZGC
Modern production applications choose collectors based on throughput vs. pause-time requirements:
| Aspect | G1 GC (Garbage First) | ZGC (Z Garbage Collector) |
|---|---|---|
| Default Since | Java 9 | Available since Java 15+ |
| Max Heap Support | Tens of GBs | Up to 16TB |
| STW Pause Target | User configurable (~200ms default) | Sub-millisecond (independent of heap size) |
| Strategy | Divides heap into thousands of regions, collects highest-garbage regions first | Concurrent compaction using Colored Pointers and Load Barriers |
| Best For | Balanced throughput and latency workloads | Low-latency applications, large heaps (e.g. caching, microservices) |
6. Interpret this GC Log Snippet
Consider the following legacy GC log line:
[GC [ParNew: 1512K->64K(1512K), 0.0635032 secs] 15604K->13569K(600345K), 0.0636056 secs] [Times: user=0.03 sys=0.00, real=0.06 secs]
Diagnostic Breakdown:โ
- Type of GC: Minor GC (signaled by
[GCprefix; if it were a Full GC, it would read[Full GC). - Collector Used: ParNew (Parallel Young Generation collector, running multi-threaded).
- Young Generation Memory Change:
1512K->64Kโ Young generation occupancy dropped from 1512KB to 64KB. - Young Generation Max Capacity:
(1512K)โ The total size allocated to the Young Generation is 1512KB. - Total Heap Occupancy Change:
15604K->13569Kโ The total occupied memory of the entire heap (Young + Old) dropped from 15604KB to 13569KB. - Total Heap Size:
(600345K)โ The total size of the heap is 600,345KB (~600MB). - Execution Time:
0.0636056 secsโ The GC pause lasted 63.6 milliseconds. - Time Metrics:
user=0.03: CPU time spent in user-space threads.sys=0.00: CPU time spent in system (kernel) threads.real=0.06: Actual wall-clock time elapsed.
