Mastering Application Performance: A Guide to VisualVM Monitoring in Java

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

  1. JDK 8 and Earlier: VisualVM is included in the JDK bin directory. Simply run jvisualvm from the command line.
  2. 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:

  1. Capture Heap Dump: Right-click on application → "Heap Dump"
  2. 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

  1. Monitor in Production-Like Environments:
    • Use similar JVM settings and hardware as production
    • Monitor under realistic load conditions
  2. Establish Baselines:
    • Know what "normal" looks like for your application
    • Monitor key metrics: heap usage patterns, GC frequency, thread counts
  3. Use Profiling Judiciously:
    • Profiling adds overhead - use it for short periods
    • Focus on specific areas rather than entire application
  4. Correlate Metrics:
    • Look for relationships between metrics
    • Example: High CPU usage + frequent GC might indicate memory pressure
  5. Combine with Other Tools:
    • Use VisualVM alongside application logs
    • Integrate with APM tools for distributed tracing

Common Issues and VisualVM Signatures

ProblemVisualVM Indicators
Memory LeakHeap graph shows steady upward trend without returning to baseline after GC
CPU BottleneckHigh CPU usage in sampler, specific methods dominating execution time
Thread DeadlockThreads tab shows deadlock detection, threads stuck in "MONITOR" state
GC OverheadMonitor shows high GC activity, little time spent in application code
ClassLoader IssuesSteady 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.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper