Wells Fargo Lead Java Engineer · Interview Prep

Week 1
Java Foundation
& Concurrency

JVM internals, garbage collection, thread lifecycle, memory model, locks, thread pools, CompletableFuture — everything examiners test at Lead level.

🔥 First Elimination Round JVM Architecture GC Deep Dive Java Memory Model Concurrency Patterns Thread Pools CompletableFuture
01
Chapter One
JVM Architecture & Runtime Memory
How the JVM actually runs your Java code — from bytecode to machine instructions. The foundation of every performance discussion.
⚙️
JVM Runtime Memory Areas
Heap · Stack · Metaspace · Code Cache
Critical
Memory Layout — Animated Visual
JVM Runtime Memory Areas
Heap
Young Gen (Eden+S0+S1) ← New objects │ Old Gen ← Long-lived
← Eden (~80%) S0 S1 Old Generation (Tenured) →
Stack
Per-thread · Method frames · Local variables · Primitives · References
Metaspace
Class metadata · Method bytecodes · Static vars (native memory — no fixed cap)
Code Cache
JIT compiled native code (hot methods)
Key Distinctions
AreaStoresScopeGC Managed?OOM Risk
HeapObjects, arrays, class instancesAll threads shareYesYes — most common
StackLocal vars, method frames, primitivesPer threadNoStackOverflowError
MetaspaceClass metadata, static fieldsJVM-widePartialYes — class leaks
Code CacheJIT compiled native codeJVM-wideNoDeoptimisation
PC RegisterCurrent instruction addressPer threadNoNo
🎤 Interview Question

What is the difference between Heap and Stack in Java? Where are objects stored?

✅ Lead-Level Answer
  • Stack: stores method call frames, local primitive variables, and object references. LIFO. Thread-private. Very fast. Fixed size per thread.
  • Heap: stores the actual objects. All threads share it. GC manages it. Larger but slower allocation.
  • When you write Person p = new Person() — the reference p is on Stack, the Person object is on Heap.
  • Escape Analysis (JIT optimisation): if JVM proves an object doesn't escape the method, it may allocate it on Stack (stack allocation) — zero GC pressure.
⚠️ Trap Answer to Avoid

Many candidates say "primitives are always on Stack". Wrong — a primitive field inside an object lives on the Heap with the object.

🎤 Interview Question

What changed from PermGen (Java 7) to Metaspace (Java 8)? Why?

✅ Answer
  • PermGen: fixed-size JVM heap region for class metadata → frequent OutOfMemoryError: PermGen space in apps that dynamically load many classes (OSGi, app servers, Groovy).
  • Metaspace: uses native memory, grows dynamically. No fixed cap (unless you set -XX:MaxMetaspaceSize).
  • Eliminates most "PermGen Full" OOMs. But unbounded growth can exhaust native memory — always set a max in production.
JVM Startup Sequence
JVM Boot Sequence
1. OS loads JVM (libjvm.so / jvm.dll)
2. Bootstrap ClassLoader loads core java.* classes from rt.jar / modules
3. JVM creates main thread + sets up heap, stack, method area
4. Main class is loaded by Application ClassLoader
5. Bytecode verified → Interpreter begins execution
6. JIT watches hot methods (invocation count threshold ~10,000)
7. Hot methods compiled to native machine code → stored in Code Cache
8. GC runs in background threads managing Heap
📦
ClassLoader & Class Loading Lifecycle
Loading → Linking → Initialization
Important
Class Loading Pipeline
Class Loading Lifecycle — Every Class Goes Through This
Bootstrap CL
java.*, javax.*
Platform CL
ext modules
App CL
classpath
LOADING
read .class
Linking
Verify
Prepare
Resolve
INIT
run <clinit>
PhaseWhat HappensInterview Detail
LoadingRead .class bytecode → create Class object in HeapParent delegation model: always asks parent first
VerificationBytecode verifier checks for safety violationsPrevents malicious/corrupted bytecode execution
PreparationAllocate memory for static fields, set default valuesstatic int x → x=0 here, not yet your assigned value
ResolutionResolve symbolic references to direct referencesCan be lazy (JIT time) or eager
InitialisationRun static initializers & assign static field values<clinit> is thread-safe (synchronised by JVM)
🎤 Interview Question

What is the parent delegation model in ClassLoading? Why does it exist?

✅ Answer

Before loading a class, a ClassLoader always delegates to its parent first. Only if parent can't find it does the child load it. This ensures:

  • Security: core Java classes (java.lang.String) always loaded by Bootstrap CL — no user class can hijack them.
  • Uniqueness: same class isn't loaded twice by different loaders (avoiding ClassCastException between two "same" classes).

Breaking parent delegation (e.g. in OSGi, servlet containers) requires custom ClassLoader — common in app servers like Tomcat.

02
Chapter Two
Garbage Collection Algorithms
GC tuning is a superpower for lead engineers. Know G1GC and ZGC cold — they come up in every performance and microservices discussion.
🗑️
GC Deep Dive — All Algorithms
Serial · Parallel · CMS · G1GC · ZGC · Shenandoah
Critical
Serial GC
-XX:+UseSerialGC
STW PauseVery High
ThroughputLow
Heap SizeSmall

Single-threaded GC. Single CPU / small heap only.

Parallel GC
-XX:+UseParallelGC
STW PauseMedium
ThroughputHigh ✓
Heap SizeMedium

Multiple GC threads. Best for batch jobs.

CMS
Deprecated (Java 14)
STW PauseLow
ThroughputMedium
FragmentationYes ✗

Concurrent mark-sweep. No compaction = fragmentation over time.

G1GC
Default Java 9+ ← USE THIS
STW PausePredictable
ThroughputHigh ✓
Compacts?Yes ✓

Region-based. Set pause goal. Best balance for services.

ZGC
-XX:+UseZGC
STW Pause< 1ms ✓✓
ThroughputMedium
Heap SizeUp to 16TB

Concurrent everything. Sub-millisecond pauses. Future standard.

G1GC Region Model — The Key Mental Model
G1GC Heap = Equal-Sized Regions (1–32MB each)
E
E
O
S
O
E
O
S
E
E = Eden S = Survivor O = Old — = Free

G1 collects regions with highest garbage % first (Garbage-First). Mixed collections target both Young and Old regions.

🎤 Interview Question

Why is G1GC preferred over CMS for payment microservices at Wells Fargo?

✅ Production-Grade Answer
  • Predictable pauses: set -XX:MaxGCPauseMillis=200 — G1 honours it. CMS has no pause goal.
  • No fragmentation: G1 compacts regions. CMS doesn't compact → fragmentation → OOM with free heap space.
  • Better for 4GB+ heaps: relevant for banking services with large session caches.
  • Concurrent marking: reduces STW windows → lower P99 latency.
  • CMS is removed from Java 14+ — future-proof decision.

For SLA-bound payment APIs where a 2-second pause = P1 incident, G1GC's bounded pause guarantee is a compliance requirement.

💡 Bonus Point

Mention ZGC for next-gen: sub-millisecond pauses, fully concurrent — ideal for real-time trade processing. But higher CPU overhead than G1GC.

JVM Flags — Payment Microservice
# Production G1GC tuning for payment service
-XX:+UseG1GC
-Xms2g -Xmx4g                      # set both equal → no resize pauses
-XX:MaxGCPauseMillis=150            # target pause goal (ms)
-XX:G1HeapRegionSize=16m            # for 4GB heap; must be power of 2
-XX:G1NewSizePercent=20             # min Young gen 20%
-XX:G1MaxNewSizePercent=40          # max Young gen 40%
-XX:+HeapDumpOnOutOfMemoryError
-XX:HeapDumpPath=/logs/heap.hprof
-Xlog:gc*:file=/logs/gc.log:time,uptime,level,tags:filecount=5,filesize=20m

# Diagnose OOM
jmap -dump:format=b,file=heap.hprof <pid>   # heap dump
jstack <pid>                                  # thread dump
jstat -gcutil <pid> 1000                      # GC stats every 1s
03
Chapter Three
Thread Lifecycle & Java Memory Model
The most-asked topic for Lead roles. Volatile, happens-before, visibility, atomicity — you must know the difference cold.
🧵
Thread Lifecycle — 6 States
NEW · RUNNABLE · BLOCKED · WAITING · TIMED_WAITING · TERMINATED
Critical
Java Thread State Machine
NEW
created, not started
RUNNABLE
ready or on CPU
BLOCKED
waiting for monitor lock
WAITING
wait() / join()
TIMED_WAIT
sleep() / wait(ms)
TERMINATED
run() completed
NEW → RUNNABLE: thread.start() RUNNABLE → BLOCKED: waiting for lock BLOCKED → RUNNABLE: lock acquired RUNNABLE → WAITING: wait() / join() WAITING → RUNNABLE: notify() / interrupt()
🧠
Java Memory Model — volatile, happens-before
Visibility · Ordering · Atomicity — the three pillars
Most Tricky
Why Shared Variables Are Dangerous
CPU Cache Visibility Problem (Without volatile)
CPU Core 1 — Thread 1
L1 Cache
flag = true ✓
just written here
Main Memory
flag = false
not yet flushed
CPU Core 2 — Thread 2
L1 Cache
flag = false ✗
stale cached value!

Thread 1 writes flag=true to its CPU cache. Main memory not updated yet. Thread 2 reads stale false from its own cache.

🔥 Most Asked Lead-Level Question

Why does volatile NOT guarantee thread safety? What does it actually provide?

✅ Expert Answer — This is How You Separate Yourself

volatile provides exactly two guarantees:

  • Visibility: writes immediately flushed to main memory; reads always from main memory (no CPU cache)
  • Happens-Before: all writes before a volatile write are visible to all reads after the corresponding volatile read

But NOT atomicity. The operation count++ is three bytecode instructions: GETFIELD, IADD, PUTFIELD. Two threads can interleave between GET and PUT → lost update.

When volatile IS sufficient: a single flag written by one thread, read by many. Example: volatile boolean running = true; — one writer, multiple readers. Safe.

When volatile is NOT sufficient: compound read-modify-write operations. Use AtomicInteger (CAS) or synchronized.

⚠️ Candidate Trap

Saying "volatile makes operations atomic" is wrong. Many candidates confuse visibility and atomicity. This answer alone can fail a lead-level candidate.

VolatileVsAtomic.java
// ❌ Race condition — volatile doesn't help here
private volatile int count = 0;
public void increment() {
    count++; // READ → ADD → WRITE (3 ops, not atomic!)
}

// ✅ Safe — AtomicInteger uses CAS (Compare-And-Swap)
private final AtomicInteger count = new AtomicInteger(0);
public void increment() {
    count.incrementAndGet(); // hardware CAS — truly atomic
}

// ✅ volatile IS safe for flag pattern (one writer, many readers)
private volatile boolean running = true;
public void stop() { running = false; } // one write
public void run()  { while(running) { /*work*/ } } // reads only

// ✅ Double-Checked Locking — volatile REQUIRED for correctness
private volatile static Singleton instance;
public static Singleton getInstance() {
    if (instance == null) {              // first check (no lock)
        synchronized (Singleton.class) {
            if (instance == null)         // second check (with lock)
                instance = new Singleton();
        }
    }
    return instance;
}
04
Chapter Four
Locks, Synchronisation & Deadlocks
synchronized vs ReentrantLock vs StampedLock vs ReadWriteLock — know when to use each and why.
🔒
Lock Types & Deadlock Prevention
synchronized · ReentrantLock · ReadWriteLock · StampedLock
Critical
synchronized
Built-in monitor lock
  • Reentrant by default
  • No timeout support
  • Automatic release
  • Cannot interrupt
  • JVM-optimised (biased locking)
ReentrantLock
java.util.concurrent.locks
  • tryLock(timeout) support
  • Interruptible lock wait
  • Fair/unfair mode
  • Multiple conditions
  • Must unlock() in finally
ReadWriteLock
Multiple readers OR one writer
  • High read, rare write
  • Readers don't block each other
  • Writer gets exclusive lock
  • Good for cache/config
  • Writer starvation risk
StampedLock
Java 8 — optimistic reads
  • Optimistic read (no lock!)
  • Validate stamp after read
  • Upgrade to write if needed
  • Best for read-heavy + low contention
  • Not reentrant — be careful
DeadlockAndFix.java
// ❌ DEADLOCK — Thread A holds L1 wants L2, Thread B holds L2 wants L1
synchronized(lock1) {
    synchronized(lock2) { /* ... */ } // Thread A
}
synchronized(lock2) {
    synchronized(lock1) { /* ... */ } // Thread B — deadlock!
}

// ✅ FIX 1: Consistent lock ordering
synchronized(lock1) { // ALWAYS acquire lock1 before lock2
    synchronized(lock2) { /* both threads follow same order */ }
}

// ✅ FIX 2: tryLock with timeout (ReentrantLock)
if (lock1.tryLock(100, TimeUnit.MILLISECONDS)) {
    try {
        if (lock2.tryLock(100, TimeUnit.MILLISECONDS)) {
            try { /* critical section */ }
            finally { lock2.unlock(); }
        }
    } finally { lock1.unlock(); }
}

// ✅ FIX 3: Use CAS / lock-free for payment counters
AtomicLong balance = new AtomicLong(1000L);
balance.addAndGet(-amount); // no lock needed
🎤 Interview Question

What are the four conditions for a deadlock? How would you detect one in production?

✅ Answer

Coffman Conditions (all 4 must hold simultaneously):

  • Mutual Exclusion: resource held exclusively
  • Hold and Wait: process holds resources while waiting for more
  • No Preemption: resource can't be forcibly taken
  • Circular Wait: A waits for B, B waits for A

Detection in production: jstack <pid> dumps all threads — JVM marks deadlocked threads with "Found one Java-level deadlock". Set up alerts on thread dump analysis in APM (Dynatrace/AppDynamics).

05
Chapter Five
Thread Pools & ExecutorService
ThreadPoolExecutor parameters, rejection policies, and why the wrong queue type can bring down your payment service.
🏊
ThreadPoolExecutor — Parameter Deep Dive
corePoolSize · maxPoolSize · Queue · RejectionPolicy
Critical
Visual: Request Flow Through Thread Pool
ThreadPoolExecutor Internals
Incoming Tasks
Task 8
Task 9
Task 10
Queue (bounded!)
Task 5
Task 6
Task 7
Core Threads (3)
Thread-1 [Task 1]
Thread-2 [Task 2]
Thread-3 [Task 3]
+
Extra Threads (2)
Thread-4 [Task 4]
Thread-5 [idle]

Flow: Task arrives → core threads busy? → queue → queue full? → create extra threads (up to max) → max reached? → RejectionPolicy

ParameterMeaningProduction Advice
corePoolSizeThreads always alive even if idleCPU-bound: = Runtime.getRuntime().availableProcessors()
maximumPoolSizeMax threads under loadIO-bound: can be higher (e.g. 4×CPU). CPU-bound: keep = core
keepAliveTimeIdle extra threads die after this60s default. Tune for burst patterns
workQueueQueue for pending tasks when all threads busy⚠️ NEVER use unbounded LinkedBlockingQueue in prod!
RejectionHandlerWhat to do when queue + max threads fullCallerRunsPolicy = natural backpressure (preferred)
⚠️ Production Danger — Executors.newFixedThreadPool()

Executors.newFixedThreadPool(10) uses unbounded LinkedBlockingQueue. Under high load, tasks queue indefinitely → OOM. Always use ThreadPoolExecutor with bounded queue and a rejection policy in production financial systems.

ProductionThreadPool.java
// ✅ Production-safe thread pool for payment processing
ThreadPoolExecutor executor = new ThreadPoolExecutor(
    10,                          // corePoolSize
    20,                          // maximumPoolSize
    60L, TimeUnit.SECONDS,       // keepAliveTime
    new ArrayBlockingQueue<>(500), // BOUNDED queue — critical!
    new ThreadFactory() {         // named threads for jstack
        private final AtomicInteger i = new AtomicInteger();
        public Thread newThread(Runnable r) {
            return new Thread(r, "payment-worker-" + i.incrementAndGet());
        }
    },
    new ThreadPoolExecutor.CallerRunsPolicy() // backpressure!
);

// Difference: submit() vs execute()
executor.execute(task);            // fire-and-forget, no return value
Future<?> f = executor.submit(task); // returns Future — can get result/exception
Rejection PolicyBehaviourUse Case
AbortPolicy (default)Throws RejectedExecutionExceptionFail fast — let caller handle
CallerRunsPolicyCaller thread runs the task itselfNatural backpressure — preferred for payments
DiscardPolicySilently drops the taskNever for financial data!
DiscardOldestPolicyDrops oldest queued task, retries newLow-priority event pipelines
06
Chapter Six
CompletableFuture & ForkJoinPool
Async orchestration for payment flows, parallel calls, and composing async operations without callback hell.
CompletableFuture Pipeline
supplyAsync · thenApply · thenCombine · exceptionally
Important
CompletableFuture — Async Payment Pipeline
Starts async
supplyAsync(() → fetchPayment())
Transform
.thenApplyAsync(p → validatePayment(p))
Consume
.thenAcceptAsync(p → processPayment(p))
Error
.exceptionally(e → { log(e); return null; })
CompletableFuturePatterns.java
// Pattern 1: Sequential pipeline
CompletableFuture.supplyAsync(() -> fetchPayment(id), executor)
    .thenApplyAsync(PaymentService::validate, executor)
    .thenApplyAsync(PaymentService::process, executor)
    .whenComplete((result, ex) -> {
        if (ex != null) handleError(ex);
        else sendNotification(result);
    });

// Pattern 2: Parallel calls + combine (fraud check + account check)
CompletableFuture<Boolean> fraud = CompletableFuture.supplyAsync(() -> checkFraud(p));
CompletableFuture<Boolean> balance = CompletableFuture.supplyAsync(() -> checkBalance(p));
CompletableFuture<Void> both = CompletableFuture.allOf(fraud, balance);
both.join(); // wait for both

// Pattern 3: anyOf — first to respond wins (multiple FX rate providers)
CompletableFuture.anyOf(provider1, provider2, provider3)
    .thenApply(rate -> convertCurrency(amount, (Double) rate));

// Pattern 4: timeout (Java 9+)
CompletableFuture.supplyAsync(() -> callExternalBank())
    .orTimeout(3, TimeUnit.SECONDS)       // throws TimeoutException
    .completeOnTimeout(fallback, 3, TimeUnit.SECONDS); // returns fallback
🎤 Interview Question

What thread pool does CompletableFuture use by default? Why can that be dangerous?

✅ Answer

By default, CompletableFuture.supplyAsync() uses ForkJoinPool.commonPool() — a JVM-wide shared pool.

Danger: all CompletableFutures in your app share this pool. If a long-running task (e.g. slow DB call) fills the pool, all async operations in the JVM are starved — including health checks and other microservices on the same JVM.

Best practice: always pass a custom Executor — especially for IO-bound operations.

⚠️ ForkJoinPool.commonPool() caveat

It uses daemon threads. If your main thread exits, tasks may not complete. In Spring Boot this usually isn't an issue, but in standalone apps — be careful.

ForkJoinPool — Work Stealing
ForkJoinPool — Work Stealing Algorithm
ForkJoinPool — N worker threads (default: CPU count)
Worker 1
SubTask A1
SubTask A2
Worker 2
SubTask B1
← stolen from W3
Worker 3
SubTask C1
empty — steals

Idle workers steal tasks from the tail of busy workers' deques. Minimises CPU idle time for recursive divide-and-conquer algorithms.

07
Chapter Seven
Concurrent Collections
ConcurrentHashMap internals, CopyOnWriteArrayList, BlockingQueue — the building blocks of thread-safe data sharing.
🗂️
ConcurrentHashMap Internals + Collections Comparison
Java 7 segments vs Java 8 CAS — know the evolution
Critical
ConcurrentHashMap — Bucket Model
CHM Java 8 — 16 Buckets, Node-Level CAS

Red = locked bucket  |  Dark = tree node (bucket > 8 entries)  |  Normal = linked list

🎤 Deeply Asked Question

Explain ConcurrentHashMap internal changes from Java 7 to Java 8. Why was it redesigned?

✅ Expert Answer

Java 7: Divided into 16 Segments, each a mini-hashtable with its own ReentrantLock. Max 16 concurrent writes. Heavy memory overhead.

Java 8: Eliminated Segments entirely. Uses:

  • CAS (Compare-And-Swap) for inserting into empty buckets — no lock at all
  • synchronized on bucket head node only for non-empty bucket updates — fine-grained
  • Red-Black Tree when a bucket exceeds 8 entries (treeify threshold) — O(log n) instead of O(n)
  • get() is lock-free — uses volatile reads. No lock during reads.

Result: near-ConcurrentSkipList performance with HashMap-like simplicity.

CollectionThread Safe?StrategyBest Use CaseAvoid When
HashMapNoNoneSingle thread, local methodAny shared state
HashtableYesSynchronized every methodLegacy code onlyNew code (too slow)
ConcurrentHashMapYesBucket-level CAS + syncHigh-concurrency read/write mapsNeed iterator consistency
CopyOnWriteArrayListYesCopy array on every writeRare writes, many reads (event listeners)Frequent writes (expensive!)
LinkedBlockingQueueYesTwo locks (head+tail)Producer-consumer queuesNeed random access
ConcurrentLinkedQueueYesCAS — lock-freeHigh-throughput queuesNeed blocking behaviour
ConcurrentCollectionPatterns.java
// ConcurrentHashMap — computeIfAbsent (atomic)
ConcurrentHashMap<String, List<Payment>> paymentsByAccount = new ConcurrentHashMap<>();
paymentsByAccount.computeIfAbsent(accountId, k -> new CopyOnWriteArrayList<>())
                 .add(payment); // atomic check-then-act

// BlockingQueue — Producer-Consumer for payment events
BlockingQueue<PaymentEvent> queue = new LinkedBlockingQueue<>(1000);

// Producer thread
queue.put(event);     // blocks if queue full — backpressure!
queue.offer(event, 100, TimeUnit.MS); // timeout variant

// Consumer thread
PaymentEvent e = queue.take(); // blocks until available

// ThreadLocal — per-thread request context (no synchronization needed)
private static final ThreadLocal<RequestContext> ctx = new ThreadLocal<>();
ctx.set(new RequestContext(userId, traceId)); // set in filter
ctx.get();                                     // access anywhere in call stack
ctx.remove();                                  // MUST remove — prevents memory leak!
⚠️ ThreadLocal Memory Leak — Classic Production Bug

In thread pools, threads are reused. If you set a ThreadLocal value and don't call remove(), the next request on the same thread sees stale data. Worse — in app servers, this causes a classloader memory leak. Always clean up in a finally block or a servlet filter.

Q
Quick Fire Round
Week 1 Interview Question Bank
All likely Week 1 questions with the answer skeleton. Practise saying these out loud.
🎤
Full Question Bank — Java Foundation
30 questions · answer skeletons · traps highlighted
Practice These
JVM & Memory
Q1

What happens when you run java HelloWorld?

Answer Skeleton

JVM starts → Bootstrap CL loads core classes → App CL loads HelloWorld.class → bytecode verified → main thread created → interpreter runs bytecode → JIT compiles hot methods → GC manages heap throughout.

Q2

How does JIT compilation work? What are C1 and C2 compilers?

Answer Skeleton

JVM starts interpreted. JIT profiles hot methods (invocation counter). C1 = client compiler, fast compilation, light optimisations. C2 = server compiler, slower but aggressive optimisations (inlining, loop unrolling, escape analysis). HotSpot uses tiered compilation: interpret → C1 → C2 for hottest code.

Q3

What causes OutOfMemoryError? Name 3 types.

Answer Skeleton
  • Java heap space: objects fill heap. Check for memory leaks (e.g. static collections, listener not removed).
  • Metaspace: too many dynamically loaded classes. Common in hot-reload frameworks.
  • GC overhead limit exceeded: GC spends >98% time recovering <2% heap. Basically heap is full.
  • Direct buffer memory: NIO ByteBuffer.allocateDirect() exhausts native memory.
Concurrency
Q4

Difference between Callable and Runnable?

Answer

Runnable.run() returns void, can't throw checked exceptions. Callable.call() returns a value (wrapped in Future), can throw checked exceptions. Use Callable when you need a result or need to propagate checked exceptions.

Q5

What is a race condition? Give a real example.

Answer

Outcome depends on thread scheduling order. Example: two threads read balance=100, both deduct 50 → both write 50 back → final balance 50 instead of 0. Lost update. Fix with synchronization or AtomicLong.compareAndSet().

Q6

What is the happens-before relationship?

Answer

A happens-before guarantee ensures that all actions (writes) before a certain point are visible to all actions after. Key rules: program order, monitor lock/unlock, volatile write/read, thread start/join, object construction completion.

Q7

What is livelock? How is it different from deadlock?

Answer

Deadlock: threads are blocked waiting. Livelock: threads keep responding to each other and changing state but make no progress. Like two people in a corridor stepping side-to-side to let each other pass forever. Both are infinite waits but livelock threads are "active".

Q8

What is thread starvation?

Answer

A thread never gets CPU time because higher-priority threads monopolise the scheduler, or an unfair lock means some threads always win. Example: with default unfair ReentrantLock, a heavily contested lock can starve some threads. Fix: use fair lock mode or redesign access pattern.

Q9

When would you use synchronized vs ReentrantLock?

Answer
  • Use synchronized: simple critical sections, method-level locking, JVM can optimise with biased/lightweight locking
  • Use ReentrantLock: need tryLock timeout, interruptible wait, fair ordering, multiple condition variables, hand-over-hand locking
Q10

How does ConcurrentHashMap.compute() differ from get() + put()?

Answer

get() + put() is not atomic — another thread can modify between the two calls (check-then-act race condition). compute() / computeIfAbsent() / merge() are atomic — the compute function executes under bucket lock. Use compute() for atomic read-modify-write on a CHM entry.

GC & Performance
Q11

What is a Stop-The-World (STW) pause? Why does it happen?

Answer

JVM halts all application threads so GC can safely traverse the object graph without concurrent modifications. Necessary for consistent heap scanning. G1GC minimises STW to concurrent marking phases, keeping major STW only for evacuation pauses. ZGC makes almost everything concurrent — STW under 1ms.

Q12

What is String Pool? Where does it live in Java 8+?

Answer

String literals are interned in the String pool to avoid duplicate objects. In Java 7 and earlier, String pool was in PermGen. From Java 7u45+, it moved to the Heap — so interned strings are GC-eligible. String.intern() forces pooling of runtime strings.