Pillar 1 — Java & JVM Internals

Deep Java — not just syntax, but how the runtime actually works
← Back to Tracker

Core Java Depth

Full-Focus
Tip: Interviewers at top-tier will probe thread safety and GC pressure. Know how to explain them on a whiteboard.

Concurrency

Thread Lifecycle

Understand the states a Java thread passes through (NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED) and the transitions between them. SDE 2 interviews often test this as a lead-in to deeper concurrency questions.

  • A thread moves from NEW to RUNNABLE when start() is called, not run()
  • BLOCKED state occurs when waiting to enter a synchronized block
  • WAITING vs TIMED_WAITING: indefinite vs bounded wait (wait() vs wait(timeout))
  • Thread.sleep() does not release the monitor lock
  • Use jstack to inspect thread states in production

volatile & happens-before

The volatile keyword guarantees visibility across threads and establishes a happens-before relationship. Critical for understanding the Java Memory Model (JMM) and why some race conditions exist despite seemingly safe code.

  • volatile prevents instruction reordering and ensures reads see the latest write
  • Happens-before: if action A happens-before B, A's results are visible to B
  • volatile is NOT atomic for compound operations like i++
  • Use AtomicInteger / AtomicReference when you need atomicity

synchronized Blocks

The foundational mutual-exclusion mechanism in Java. Know the difference between method-level and block-level synchronization, and how the JVM implements it via monitor locks.

  • Every Java object has an intrinsic monitor lock (monitor enter / monitor exit bytecodes)
  • Synchronized on this vs a dedicated lock object — the latter is safer
  • Static synchronized methods lock on the Class object
  • JVM optimizations: biased locking, lightweight locking, lock coarsening

ReentrantLock / ReadWriteLock

Explicit lock APIs from java.util.concurrent.locks that offer more control than synchronized — fairness policies, interruptible locking, and try-lock with timeout.

  • ReentrantLock allows the same thread to acquire the lock multiple times (reentrant)
  • tryLock(timeout) prevents indefinite blocking — key for deadlock avoidance
  • ReadWriteLock: multiple readers OR one writer — major throughput gain for read-heavy workloads
  • Always use lock() / unlock() in a try-finally block

ThreadLocal

Provides thread-confined variables — each thread gets its own independent copy. Commonly used for user context, database connections, and date formatters in web applications.

  • Each thread accesses its own isolated copy via get() / set()
  • Memory leak risk: always call remove() in a finally block, especially in thread pools
  • InheritableThreadLocal passes values to child threads
  • Spring uses ThreadLocal for RequestContextHolder and transaction contexts

Executor Framework

The standard way to manage thread pools in Java. Interviewers expect SDE 2 candidates to know pool types, rejection policies, and when to use which pool.

  • FixedThreadPool: bounded pool, tasks queue up if all threads busy
  • CachedThreadPool: unbounded, creates threads on demand — risk of OOM under load
  • ScheduledThreadPool: for periodic / delayed tasks
  • Rejection policies: AbortPolicy (default), CallerRunsPolicy, DiscardPolicy, DiscardOldestPolicy
  • Always use shutdown() + awaitTermination() for graceful cleanup

Fork/Join Pool

A specialized pool for divide-and-conquer parallelism. Uses work-stealing to keep all threads busy. Powers parallelStream() behind the scenes.

  • Extends RecursiveTask<V> (returns result) or RecursiveAction (void)
  • Work-stealing: idle threads steal tasks from busy threads' queues
  • ForkJoinPool.commonPool() is shared across the JVM — be careful in web servers
  • Best for CPU-bound tasks that can be recursively split

CompletableFuture Chains

The modern way to compose asynchronous operations in Java. Replaces callback hell with fluent, chainable stages. A frequent SDE 2 interview topic.

  • thenApply (sync transform), thenCompose (flatMap), thenCombine (merge two futures)
  • supplyAsync runs on ForkJoinPool by default — pass custom executor for IO-bound work
  • exceptionally and handle for error recovery
  • allOf / anyOf to fan-out and aggregate

Deadlock Detection

Deadlock happens when two or more threads wait forever for locks held by each other. Interviewers love asking candidates to identify, prevent, or resolve deadlocks from code snippets.

  • Four conditions: mutual exclusion, hold-and-wait, no preemption, circular wait
  • Prevention: always acquire locks in a consistent global order
  • tryLock(timeout) breaks the hold-and-wait condition
  • Detect with jstack — it reports deadlocked threads automatically
  • ThreadMXBean.findDeadlockedThreads() for programmatic detection

Collections Internals

HashMap Rehashing & Collision

How HashMap works under the hood is arguably the most asked Java interview question. Know the internal array + linked-list/tree structure, load factor, and treeification threshold.

  • Default capacity 16, load factor 0.75 — rehash doubles the array when 75% full
  • Hash collision: same bucket index. Resolved with linked list, then red-black tree at 8 nodes (Java 8+)
  • Rehashing redistributes all entries — O(n) cost, can cause latency spikes
  • Key must implement hashCode() and equals() consistently
  • NOT thread-safe — use ConcurrentHashMap in concurrent contexts

LinkedHashMap Order

A HashMap that also maintains a doubly-linked list of entries to preserve insertion order (or access order). The basis for LRU cache implementations in interviews.

  • Insertion-order by default; set accessOrder=true for LRU behavior
  • Override removeEldestEntry() to build a bounded LRU cache
  • Slightly more memory overhead than HashMap (prev/next pointers per entry)

ConcurrentHashMap Segments

The go-to thread-safe map. Java 8 replaced the old segment-lock design with per-bucket CAS + synchronized nodes, dramatically improving concurrency.

  • Java 7: segmented locking (16 segments by default). Java 8+: node-level locking with CAS
  • Read operations are lock-free (volatile reads)
  • computeIfAbsent is atomic — a clean alternative to double-check idioms
  • Does NOT allow null keys or null values (unlike HashMap)

TreeMap / Comparators

A red-black tree based NavigableMap that keeps keys sorted. Know when to choose TreeMap over HashMap and how comparators affect behavior.

  • O(log n) for get/put/remove — vs O(1) amortized for HashMap
  • Keys must be Comparable or a Comparator must be provided at construction
  • floorKey, ceilingKey, subMap — powerful range queries
  • Comparator consistency with equals() is important — inconsistency breaks Map contract

PriorityQueue Heap

Backed by a min-heap (binary heap array). Essential for top-K, merge-K-sorted, and scheduling problems in coding interviews.

  • Default is min-heap. Use Collections.reverseOrder() or custom comparator for max-heap
  • offer/poll are O(log n), peek is O(1)
  • remove(Object) is O(n) — avoid if performance matters
  • NOT thread-safe — use PriorityBlockingQueue for concurrent access

ArrayDeque vs LinkedList

ArrayDeque is faster than LinkedList for both stack and queue use cases. Know why, and when LinkedList still makes sense.

  • ArrayDeque: resizable circular array, no node allocation — better cache locality
  • LinkedList: doubly-linked nodes, each allocation = GC pressure
  • ArrayDeque does NOT allow null elements
  • Use ArrayDeque as stack (push/pop) and queue (offer/poll) — avoid Stack class (legacy, synchronized)

Memory & GC

Heap Regions (Eden / S0 / S1 / Old / Metaspace)

The JVM heap is divided into generational regions. Understanding this layout is fundamental to tuning GC and diagnosing memory issues in production.

  • Young Gen: Eden (new objects) + Survivor spaces S0/S1 (objects that survived minor GC)
  • Old Gen (Tenured): long-lived objects promoted after surviving multiple minor GCs
  • Metaspace (Java 8+): stores class metadata, replaces PermGen, grows natively
  • Minor GC collects Young Gen; Major/Full GC collects Old Gen — much more expensive
  • Most objects die young (generational hypothesis) — this is why generational GC works

GC Algorithms (G1, ZGC, CMS)

Different collectors optimize for different goals — throughput, latency, or footprint. SDE 2 interviews test whether you can pick the right one for a given scenario.

  • G1 (default since Java 9): region-based, targets pause-time goals, good general-purpose choice
  • ZGC (Java 15+): sub-millisecond pauses even on multi-TB heaps, concurrent
  • CMS (deprecated Java 9, removed Java 14): low-pause but fragmentation problems
  • Serial / Parallel: throughput-oriented, acceptable for batch jobs

GC Tuning Flags

Knowing the key JVM flags lets you tune garbage collection behavior for your application's workload. Expect "how would you tune GC for X" questions.

  • -XX:MaxGCPauseMillis=200 — G1 pause target
  • -XX:NewRatio=2 — Old/Young ratio (default: Old is 2x Young)
  • -XX:SurvivorRatio=8 — Eden/Survivor ratio
  • -XX:+UseStringDeduplication — reduces heap for string-heavy apps (G1 only)
  • -XX:MetaspaceSize / -XX:MaxMetaspaceSize — control class metadata growth

Memory Leaks & Weak Refs

Java has GC, but memory leaks still happen — typically through unintentional strong references. Know the reference types and common leak patterns.

  • Common leaks: static collections, unclosed resources, listeners not deregistered, ThreadLocal not removed
  • WeakReference: collected when no strong refs remain — used in WeakHashMap
  • SoftReference: collected only under memory pressure — good for caches
  • PhantomReference: for cleanup actions after object is collected (replaces finalize)

Object Finalization

finalize() is deprecated (Java 9+) and should never be relied on. Know why it is problematic and what modern alternatives exist.

  • finalize() runs non-deterministically, may never run at all
  • Finalizable objects require two GC cycles to collect — performance hit
  • Replacement: Cleaner API (Java 9+) or try-with-resources for AutoCloseable
  • In interviews, mentioning finalize pitfalls shows depth of JVM understanding

Java 8-17 Features

Light
Tip: Skim at office. You likely use most of these already — this is revision, not learning.

Modern Java

Streams & Lazy Evaluation

Streams provide a declarative pipeline for data processing. Understand lazy evaluation — intermediate operations build the pipeline; terminal operations trigger execution.

  • Intermediate: filter, map, flatMap, sorted, distinct — all lazy
  • Terminal: collect, forEach, reduce, count — triggers execution
  • Short-circuiting: findFirst, anyMatch, limit — stop early
  • Parallel streams use ForkJoinPool — avoid for IO-bound or small datasets

Optional Best Practices

Optional is a container for nullable values that makes intent explicit. Know the anti-patterns — Optional.get() without check, using Optional as method parameter.

  • Use orElse, orElseGet, orElseThrow — never raw get()
  • orElse always evaluates the default; orElseGet is lazy — matters for expensive defaults
  • Don't use Optional for fields, constructors, or method parameters — only return types
  • map / flatMap for chaining transformations on the wrapped value

Method References

Shorthand for lambdas that simply call an existing method. Clean, readable, and commonly used with streams and functional interfaces.

  • Four kinds: static (Class::staticMethod), instance (obj::method), arbitrary object (Class::instanceMethod), constructor (Class::new)
  • Equivalent to a lambda but more concise: x -> x.toString() becomes Object::toString
  • Cannot use method references for methods with additional logic or multiple statements

Default / Static Interface Methods

Java 8 added default and static methods in interfaces, enabling API evolution without breaking implementations. Know the diamond problem implications.

  • default provides a body that implementing classes inherit (or override)
  • Diamond problem: if two interfaces provide the same default method, the class must override it
  • static methods in interfaces are utility methods — not inherited by implementing classes
  • Java 9 added private interface methods to share code between defaults

Records & Sealed Classes

Records (Java 16) are immutable data carriers that auto-generate equals, hashCode, toString. Sealed classes (Java 17) restrict which classes can extend them — powerful for domain modeling.

  • Records: record Point(int x, int y) {} — concise, immutable, transparent
  • Records can have custom constructors and instance methods, but fields are final
  • Sealed classes: sealed class Shape permits Circle, Square {}
  • Subclasses must be final, sealed, or non-sealed

Pattern Matching instanceof

Java 16 eliminates the redundant cast after instanceof. A small but impactful readability improvement that interviewers may mention as a modern Java signal.

  • Before: if (obj instanceof String) { String s = (String) obj; ... }
  • After: if (obj instanceof String s) { ... } — binding variable s is in scope
  • Works with negation and compound conditions

Text Blocks

Java 15 text blocks use triple-quote """ syntax for multi-line strings. Eliminates escape-character noise for JSON, SQL, and HTML literals.

  • Indentation is stripped based on the position of the closing """
  • Supports \s (explicit space) and \ (line continuation) escape sequences
  • Works with String.formatted() for template-like usage

var Inference

Java 10 introduced local variable type inference with var. Reduces boilerplate but has guardrails — cannot be used for fields, parameters, or return types.

  • Only for local variables with initializers: var list = new ArrayList<String>();
  • The compiler infers the type at compile time — it is NOT dynamic typing
  • Use when the type is obvious from the right-hand side; avoid when it hurts readability

JVM Tuning & Tooling

Full-Focus

Profiling Tools

jstack / jmap / jstat

The essential JDK command-line tools for diagnosing live JVM processes. Interviewers test whether you can actually troubleshoot production issues, not just write code.

  • jstack <pid>: prints thread dump — find deadlocks, blocked threads, CPU hotspots
  • jmap -heap <pid>: heap summary. jmap -dump:format=b,file=heap.hprof <pid>: full heap dump
  • jstat -gcutil <pid> 1000: GC stats every 1s — monitor GC frequency and pause times
  • Use -l flag with jstack for extended info including lock ownership

VisualVM / JProfiler

GUI-based profiling tools for CPU profiling, memory analysis, and thread monitoring. Be familiar with their capabilities even if you primarily use command-line tools.

  • VisualVM: free, bundled with JDK (until Java 8), now separate download
  • CPU profiling: identify hot methods consuming the most CPU
  • Memory profiling: track allocation rates, find leaks via retained size analysis
  • JProfiler: commercial, richer call-tree analysis and JDBC/JPA query profiling

Heap Dump Analysis

A heap dump captures every live object on the heap at a point in time. Analyzing it lets you find memory leaks, oversized caches, and unexpected object retention.

  • Generate via jmap, JMX, or -XX:+HeapDumpOnOutOfMemoryError
  • Open in Eclipse MAT (Memory Analyzer Tool) for dominator tree and leak suspect reports
  • Look at retained size (transitive closure) vs shallow size
  • Common findings: unbounded caches, duplicate strings, classloader leaks

Thread Dump Analysis

A thread dump shows what every thread is doing at a given instant. The primary tool for diagnosing deadlocks, thread starvation, and high-latency requests.

  • Capture via jstack, kill -3 <pid>, or JMX ThreadMXBean
  • Look for: BLOCKED threads (contention), WAITING threads (possible deadlock), runnable threads in loops
  • Take 3-5 dumps a few seconds apart to spot stuck threads vs transient states
  • Modern tools: async-profiler, fastThread.io for automated analysis

GC Log Parsing

GC logs record every collection event with timing and memory details. Parsing them is how you validate tuning changes and diagnose pause-time issues.

  • Enable with -Xlog:gc*:file=gc.log:time,uptime,level,tags (Java 9+ unified logging)
  • Legacy: -XX:+PrintGCDetails -XX:+PrintGCDateStamps -Xloggc:gc.log
  • Key metrics: pause duration, allocation rate, promotion rate, heap occupancy after GC
  • Tools: GCViewer, GCEasy.io for visual analysis

JVM Flags

-Xms / -Xmx

The most fundamental JVM flags. -Xms sets initial heap size, -Xmx sets maximum heap size. Setting them equal avoids costly heap resizing at runtime.

  • -Xms512m -Xmx2g: start at 512 MB, grow up to 2 GB
  • Set -Xms == -Xmx in production to avoid resize pauses
  • Too small: frequent GC, OOM. Too large: longer GC pauses, wasted memory
  • Container-aware since Java 10: -XX:MaxRAMPercentage=75.0 for Docker

-XX:+UseG1GC

Enables the G1 (Garbage First) collector. Default since Java 9. Know when G1 is the right choice and how its region-based model differs from older collectors.

  • Divides heap into equal-sized regions (~1-32 MB), collects regions with most garbage first
  • Targets pause-time goal: -XX:MaxGCPauseMillis=200 (default)
  • Mixed GC: collects young + some old regions in the same pause
  • Good for heaps > 4 GB. For sub-ms pauses, consider ZGC instead

PrintGCDetails

Enables detailed GC logging — essential for understanding what the collector is doing and how to tune it. The first flag you add when diagnosing GC issues.

  • Legacy: -XX:+PrintGCDetails -XX:+PrintGCDateStamps
  • Java 9+ unified logging: -Xlog:gc*=info:file=gc.log:time
  • Shows: which generation collected, before/after sizes, pause duration
  • Combine with -XX:+PrintGCApplicationStoppedTime for full picture

HeapDumpOnOOM

-XX:+HeapDumpOnOutOfMemoryError automatically captures a heap dump when the JVM runs out of memory. A must-have flag for every production JVM.

  • -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/var/logs/heap.hprof
  • Without this, OOM crashes leave no forensic evidence
  • Pair with -XX:OnOutOfMemoryError="kill -9 %p" to force restart after dump
  • Analyze the dump in Eclipse MAT to find the leak source

Recommended Resources

Books

  • Java Concurrency in Practice — Brian Goetz. The definitive resource on Java threading and memory model.
  • Effective Java (3rd Ed.) — Joshua Bloch. Covers modern Java best practices including streams, optionals, and generics.
  • Java Performance (2nd Ed.) — Scott Oaks. GC tuning, JIT compilation, benchmarking with JMH.

Tools & Practice

  • VisualVM — free JVM profiling
  • Eclipse MAT — heap dump analysis
  • async-profiler — low-overhead CPU and allocation profiler for Linux/macOS
  • JMH (Java Microbenchmark Harness) — for accurate micro-benchmarks