VisualVM for Java Development
VisualVM allows you to watch the execution of your application's threads (as well as the JVM's threads) dynamically as the application runs. The result is an easy-to-read, color-coded list of threads that updates as time passes and thread states change (see the figure below). By watching thread states, you can visually note threads that are blocking on events, or objects/locks shared with other threads. This may be normal behavior or it may yield surprising behavior that you weren't able to capture in any other way. For instance, I've been able to identify unwanted thrashing between threads, needless blocking, multiple threads that were acting as one (sequentially) due to improper synchronization, and deadlocks (down to the individual threads deadlocked and the object(s) they were each waiting for).
You can also create textual thread dumps as your process runs, creating a record of thread activity you can inspect later. I've incorporated thread dumps into unit tests to ensure my concurrent code is working as designed — something that's hard to prove otherwise (see the figure below).
By using VisualVM as a testing and debugging tool, I've been able to eliminate bugs before they hit production, find them quickly when they do slip by, and then create proactive tests for the future to prevent similar problems.
Inspecting the Heap and Memory Usage
So Java eliminates all your memory worries, right? Wrong. To draw an analogy to exercise, most amateur runners try to take their mind off how they feel while in a race. However, when asked about this, Bill Rodgers (four-time winner of the Boston Marathon) said that's *all* he thinks about when he races. Likewise, although Java provides automatic memory management, if you're a pro, you should focus more than ever on memory usage and its affects.
Fortunately, VisualVM provides plenty of help here. You can watch as objects are created and collected dynamically as your application executes, create snapshots of the Java heap for later analysis, or create heap dumps that you can sort and inspect right down to the root objects within the heap. This allows you to locate pools of objects that potentially grow out of control, inefficient use of resources (such as I/O-related objects, files, and so on), and inefficient maps of data (those that use and continually create thousands of
You can create dumps of the Java heap and sort by object type (class) the total number of objects for a specific class, the largest objects, and so on. Further, you can inspect each object and drill down to find the reference(s) to it in case you've identified a memory leak. This can sometimes be a tedious task, but I've found it necessary on many occasions, and I've been grateful VisualVM gives me the ability to get to this level of detail (see the figure below).
I try to teach new programmers the importance of code profiling under various test conditions. These include idle tests, stress tests, low-memory tests, and everything in between. The result is a list of objects and methods called during your tests, the total number of times each method is called, the total time spent within the sum of those methods, and which methods take the longest to execute alone as well as in total. This is valuable information, as it shows you which methods may need to be optimized, tuned, or just rewritten (see the example below).
Remember that over-optimization can lead to needless bugs and wasted effort. However, a quick perusal of the VisualVM profile output can pinpoint potential trouble and outstanding performance issues with little time or effort spent.
The VisualGC plugin for VisualVM can be a valuable tool for some, but may be overwhelming and useless to others. If your software is sensitive to performance and latency issues, then you're probably interested in what goes on inside the Java GC. VisualGC provides graphs (and the documentation explains all of them here).
Useful information to watch for includes the size ratio between eden space (including survivor spaces S0 and S1) and the old generation (where young and older objects reside, respectively), the overall time spent collecting, and objects that get unloaded.
For instance, if objects get allocated directly within the old generation, it could indicate that your application creates very large objects that might be better broken apart. Also, excessive class unloading indicates your application is getting close to an out-of-memory situation, where the JVM is unloading loaded (and compiled) class information to make room. Not only is this a waste of processing time, it means those classes may need to be loaded and compiled again in the future.
However, GC tuning is a skill acquired through research and experience. If you're new to this, make sure you use VisualVM in conjunction with other resources on Java GC before diving in.
Happy coding (and debugging)!