In the world of Java development, writing code is only half the battle. Understanding how that code behaves in production—its memory consumption, CPU usage, thread activity, and garbage collection efficiency—is crucial for building robust, performant applications. VisualVM is a powerful, free tool that provides this vital visibility, making it an essential part of every Java developer's toolkit.
This article provides a comprehensive guide to using VisualVM for monitoring, profiling, and troubleshooting Java applications.
What is VisualVM?
VisualVM is a visual tool that integrates several command-line JDK tools and lightweight profiling capabilities. It provides a unified interface for:
- Monitoring application performance in real-time
- Profiling CPU and memory usage
- Analyzing heap dumps and thread dumps
- Troubleshooting performance issues and memory leaks
Key Advantage: It's bundled with the JDK (up to JDK 8) and remains available as a separate download for later JDK versions, making it easily accessible to all Java developers.
Getting Started with VisualVM
Installation and Setup
- JDK 8 and Earlier: VisualVM is included in the JDK bin directory. Simply run
jvisualvmfrom the command line. - JDK 9 and Later: Download and install VisualVM from the official website.
Connecting to Applications
VisualVM automatically detects locally running Java applications. For remote monitoring, you need to start your application with specific JVM arguments:
# For remote connection java -Dcom.sun.management.jmxremote \ -Dcom.sun.management.jmxremote.port=9010 \ -Dcom.sun.management.jmxremote.ssl=false \ -Dcom.sun.management.jmxremote.authenticate=false \ -jar your-application.jar
Then in VisualVM, add a remote connection with hostname:port (e.g., localhost:9010).
Key Monitoring Capabilities
1. Overview Dashboard
The Overview tab provides essential information about your application:
- JVM Version: Java version and vendor
- JVM Arguments: Command-line arguments used to start the application
- System Properties: All JVM system properties
- PID: Process identifier
2. Monitor Tab - Real-time Performance
This is the heart of VisualVM for real-time monitoring, displaying four key charts:
CPU Usage:
- CPU Usage: The percentage of CPU capacity your application is using
- GC Activity: Time spent in garbage collection
Memory Usage:
- Heap Memory: Eden, Survivor, and Old Generation usage
- Non-Heap Memory: Metaspace/PermGen, Code Cache, etc.
// Example code that creates memory pressure
public class MemoryStressExample {
public static void main(String[] args) throws InterruptedException {
List<byte[]> memoryHog = new ArrayList<>();
while (true) {
// Allocate 1MB every second
memoryHog.add(new byte[1024 * 1024]);
Thread.sleep(1000);
// Simulate some garbage creation
if (memoryHog.size() > 50) {
memoryHog.remove(0); // Create some garbage
}
}
}
}
VisualVM Insight: You'll see the sawtooth pattern of heap usage—growing until a GC occurs, then dropping sharply.
Classes:
- Loaded Classes: Number of classes loaded over time
- Unloaded Classes: Classes that have been garbage collected
Threads:
- Live Threads: Current number of active threads
- Daemon Threads: Number of daemon threads
3. Threads Tab - Concurrency Analysis
The Threads tab provides detailed insight into thread behavior:
Live Thread Visualization:
- View all threads and their states (Running, Sleeping, Waiting, Blocked)
- Identify deadlocks and thread contention
public class ThreadDeadlockExample {
private static final Object lock1 = new Object();
private static final Object lock2 = new Object();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
synchronized (lock1) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock2) {
System.out.println("Thread 1 acquired both locks");
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock2) {
try { Thread.sleep(100); } catch (InterruptedException e) {}
synchronized (lock1) {
System.out.println("Thread 2 acquired both locks");
}
}
});
t1.start();
t2.start();
}
}
VisualVM Insight: The Threads tab will clearly show a deadlock, indicating which threads are blocked and which monitors they're waiting for.
4. Sampler and Profiler Tabs - Deep Performance Analysis
Sampler:
- CPU Sampling: See which methods are consuming the most CPU time
- Memory Sampling: See which objects are occupying the most memory
Profiler:
- CPU Profiling: Instrumented profiling for precise method-level timing
- Memory Profiling: Track object allocations and garbage collection
public class CPUIntensiveExample {
public static void main(String[] args) {
while (true) {
calculatePrimes(10000); // CPU-intensive method
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
// Inefficient prime calculation to show CPU usage
private static void calculatePrimes(int n) {
for (int i = 2; i <= n; i++) {
boolean isPrime = true;
for (int j = 2; j < i; j++) {
if (i % j == 0) {
isPrime = false;
break;
}
}
}
}
}
VisualVM Insight: The CPU sampler/profiler will show that calculatePrimes is the most CPU-intensive method.
Advanced Features
Heap Dump Analysis
VisualVM can capture and analyze heap dumps to identify memory issues:
- Capture Heap Dump: Right-click on application → "Heap Dump"
- Analyze:
- Biggest Objects: See which objects are consuming the most memory
- Dominators: Find objects that prevent garbage collection of other objects
- OQL Console: Query the heap dump using Object Query Language
// Potential memory leak example
public class MemoryLeakExample {
private static final List<byte[]> LEAK = new ArrayList<>();
public static void main(String[] args) throws InterruptedException {
while (true) {
// Allocate memory but never release references
LEAK.add(new byte[1024 * 1024]); // 1MB
Thread.sleep(100);
if (LEAK.size() > 100) {
System.out.println("Allocated 100MB, continuing to leak...");
}
}
}
}
Heap Dump Insight: The heap dump will show the LEAK ArrayList holding references to all the byte arrays, preventing GC.
MBeans Browser
VisualVM includes a full MBeans browser for monitoring and managing applications through JMX:
- View and modify application settings at runtime
- Monitor custom JMX metrics
- Execute MBean operations
Best Practices for Effective Monitoring
- Monitor in Production-Like Environments:
- Use similar JVM settings and hardware as production
- Monitor under realistic load conditions
- Establish Baselines:
- Know what "normal" looks like for your application
- Monitor key metrics: heap usage patterns, GC frequency, thread counts
- Use Profiling Judiciously:
- Profiling adds overhead - use it for short periods
- Focus on specific areas rather than entire application
- Correlate Metrics:
- Look for relationships between metrics
- Example: High CPU usage + frequent GC might indicate memory pressure
- Combine with Other Tools:
- Use VisualVM alongside application logs
- Integrate with APM tools for distributed tracing
Common Issues and VisualVM Signatures
| Problem | VisualVM Indicators |
|---|---|
| Memory Leak | Heap graph shows steady upward trend without returning to baseline after GC |
| CPU Bottleneck | High CPU usage in sampler, specific methods dominating execution time |
| Thread Deadlock | Threads tab shows deadlock detection, threads stuck in "MONITOR" state |
| GC Overhead | Monitor shows high GC activity, little time spent in application code |
| ClassLoader Issues | Steady increase in loaded classes without corresponding unloading |
Conclusion
VisualVM is an indispensable tool for any Java developer serious about application performance and reliability. Its combination of real-time monitoring, deep profiling capabilities, and heap/thread analysis makes it perfect for:
- Development: Identifying performance bottlenecks early
- Testing: Verifying application behavior under load
- Production Support: Troubleshooting runtime issues
While newer tools like JDK Mission Control and async-profiler offer additional capabilities, VisualVM remains one of the most accessible and comprehensive free tools for Java performance analysis. By mastering VisualVM, you gain the ability to not just fix performance problems, but to understand the fundamental behavior of your Java applications at runtime.
Further Reading: Explore plugins for VisualVM that add features like JConsole compatibility, MBean notifications, and advanced GC analysis from the Tools → Plugins menu within VisualVM.