How do you typically approach debugging a Java application that is throwing an unexpected NullPointerException?
Answer:
I start by examining the stack trace to pinpoint the exact line of code. Then, I use a debugger to inspect the values of variables leading up to that line, looking for any uninitialized or null objects. Often, I'll add null checks or use Optional to prevent future occurrences.
Describe a scenario where you would use a Java profiler. What kind of issues does it help identify?
Answer:
I would use a profiler like VisualVM or JProfiler when an application is experiencing slow response times or high CPU/memory usage. It helps identify performance bottlenecks such as CPU-intensive methods, excessive object creation (leading to GC overhead), memory leaks, and inefficient I/O operations.
What are some common causes of OutOfMemoryError in Java, and how would you diagnose them?
Answer:
Common causes include memory leaks (objects not being garbage collected), creating too many large objects, or insufficient heap size. I'd diagnose by analyzing heap dumps (using tools like Eclipse MAT) to identify dominant objects and their references, and by monitoring GC logs to see if garbage collection is struggling.
How do you handle a Java application that is experiencing a deadlock?
Answer:
I would take a thread dump (using jstack or kill -3 <pid>) and analyze it. Deadlocks are typically visible in the thread dump, showing threads waiting indefinitely for locks held by other threads. Once identified, I'd refactor the code to ensure consistent lock acquisition order or use java.util.concurrent utilities like ReentrantLock with tryLock().
Explain the difference between a 'memory leak' and 'excessive object creation' in the context of performance tuning.
Answer:
A memory leak occurs when objects are no longer needed but are still referenced, preventing the garbage collector from reclaiming their memory. Excessive object creation, on the other hand, means too many objects are being created and then quickly discarded, leading to frequent and potentially costly garbage collection cycles, even if memory is eventually freed.
What is the purpose of JVM arguments like -Xms and -Xmx? When would you adjust them?
Answer:
-Xms sets the initial heap size, and -Xmx sets the maximum heap size for the JVM. I would adjust them when an application is experiencing OutOfMemoryError (increase -Xmx) or if garbage collection is too frequent (increase -Xms to reduce initial GC pressure) or too slow, to optimize memory usage for the specific application workload.
How can you monitor the garbage collection activity of a Java application?
Answer:
I can monitor GC activity by enabling GC logging using JVM arguments like -Xlog:gc*. This outputs detailed information about GC events, including pause times, memory reclaimed, and heap usage. Tools like VisualVM or GCViewer can then parse and visualize these logs for easier analysis.
Answer:
I would first enable SQL logging in the application or ORM framework to see the actual queries being executed. Then, I'd use database-specific tools (e.g., EXPLAIN in SQL) to analyze query execution plans, identify missing indexes, or inefficient joins. Profilers can also show time spent in database calls.
Answer:
Common pitfalls include race conditions, deadlocks, livelocks, and starvation. These often arise from improper synchronization, incorrect use of shared mutable state, or not handling thread safety correctly. Using java.util.concurrent utilities and immutable objects can mitigate many of these issues.
How do you determine if an application is CPU-bound or I/O-bound?
Answer:
I'd use a profiler to analyze CPU usage and thread states. If threads are spending most of their time in RUNNABLE state and CPU utilization is high, it's likely CPU-bound. If threads are frequently in WAITING or BLOCKED states, often waiting for network, disk, or database operations, it's I/O-bound.