1. JMX (Java Management Extensions): the heart of JVM monitoring
Monitoring is like a regular health check-up for your application. If logging can be compared to a diary that records everything that has already happened, monitoring is a set of instruments showing the system’s current state right now: temperature, pulse, blood pressure, blood sugar, and other vital JVM indicators.
It helps you understand how much memory is actually used and whether it’s leaking, how many threads are running and what state they are in — waiting, running, or blocked. You can see how often the garbage collector runs, how loaded the CPU is, and notice warning signs in time, such as a sudden rise in memory usage or thread count.
In short, logging tells you what has already happened, while monitoring shows you what’s happening right now.
JMX basics: what it is and why
JMX is a technology in Java that allows you to observe the life of your application from the inside and even control it a bit. It works through special objects — MBean (Management Bean). You can think of them as “sensors” and “switches” inside the JVM: some display the system state, others let you tweak things.
With JMX, you can learn how much memory is currently allocated to the JVM, how many threads are active, how much time garbage collection takes, and much more — without diving into code or restarting the application.
How it works
Out of the box, the JVM already has many MBeans that provide information about:
- memory (heap, non-heap);
- garbage collectors (GC);
- threads;
- classes (how many are loaded, unloaded);
- and even the JVM itself (version, startup parameters).
JMX is like a car’s dashboard. You see speed, RPM, engine temperature — and all of this is available through standard sensors (MBeans).
How to access JMX
The simplest way is to use the standard JConsole utility that comes with the JDK.
Launching JConsole
- Open the terminal/command prompt.
- Run the command:
jconsole - Select the Java process you want to monitor (for example, your application).
JConsole will connect to the JVM via JMX and display charts and tables: memory usage, number of threads, garbage collector activity, etc.
Example: inspect memory and threads
In JConsole, open the “Memory” and “Threads” tabs. You’ll see how the amount of used memory changes, how many threads are alive, which are active, and which are waiting.
Custom MBeans
You can create your own MBeans to monitor custom metrics (for example, the number of processed orders). But that’s an advanced topic: the standard MBeans already provide a lot of information.
2. VisualVM — visual monitoring and profiling
If JConsole is the “dashboard,” then VisualVM is a full diagnostic center with X-ray, MRI, and blood tests. It lets you not only monitor but also profile the application, take a heap dump, analyze leaks, and see which methods take the most time.
Installing and launching VisualVM
- VisualVM is included with the JDK (usually as jvisualvm), but you can download the latest version at visualvm.github.io.
- Launch it with:
jvisualvm - After launch, you’ll see a list of all Java processes running on your machine.
Connecting to a process
- Find your process (for example, Main or MyApp) in the list.
- Double-click it — a tab with process information will open.
- Done! Now you can see:
- CPU and memory usage in real time.
- The number of threads and their state.
- A list of loaded classes.
- The ability to take a heap dump and a thread dump.
Key features of VisualVM
With VisualVM, you can observe how memory is used — both heap and non-heap. The chart shows whether consumption is growing or stabilising. To check the garbage collector, click the “Perform GC” button — the JVM will immediately try to free memory. You can take a heap dump (a memory snapshot) and examine it for leaks.
The tool also shows thread states: how many are running, which are working, waiting, or blocked. If a deadlock occurs, VisualVM will help detect it and show the circular waits.
For deep analysis, there’s profiling: you’ll see “hot” methods that “eat” the most CPU time or memory. This helps diagnose unexpected performance drops.
Example: monitor a mini application
public class MemoryLeakDemo {
public static void main(String[] args) throws InterruptedException {
List<byte[]> memoryConsumers = new ArrayList<>();
for (int i = 0; i < 1000; i++) {
memoryConsumers.add(new byte[1024 * 1024]); // 1 MB
Thread.sleep(100); // Wait a bit so the chart is smoother
}
System.out.println("Done! Don't forget to look in VisualVM :)");
Thread.sleep(60000); // Keep the application open for analysis
}
}
Now:
- Run the program.
- Open VisualVM and connect to the process.
- Watch memory usage grow.
- Take a heap dump and find arrays that take up memory.
3. Java Flight Recorder (JFR): a black box for the JVM
What is JFR
Java Flight Recorder is a tool built into the JVM for collecting detailed events about application behaviour. It’s a “black box”: JFR writes what happens in the JVM so you can later find bottlenecks or causes of incidents.
JFR collects:
- GC events;
- thread information;
- method call frequency and time profiles;
- pauses and latencies;
- exceptions and errors.
How to enable JFR
Starting with Java 11+, JFR is included right in OpenJDK.
Starting with JFR
java -XX:StartFlightRecording=filename=recording.jfr,duration=60s,settings=profile -jar MyApp.jar
- filename=recording.jfr — where to save the recording.
- duration=60s — how long to record.
- settings=profile — detail level (default, profile, continuous).
Viewing the results
To analyze a .jfr file, use JDK Mission Control (JMC):
- Download JMC: jdk.java.net/jmc
- Open the recording.jfr file.
- Study the charts, “hot” methods, GC pauses, and thread activity.
Example: capturing the “black box”
- Launch the application with JFR (see the command above).
- Open JDK Mission Control and load the recording file.
- Assess hot spots for CPU/memory, garbage collection frequency, and the number of active threads.
4. Practice: monitor an application
import java.util.ArrayList;
import java.util.List;
public class MonitoringExample {
public static void main(String[] args) throws InterruptedException {
List<byte[]> memory = new ArrayList<>();
for (int i = 0; i < 50; i++) {
memory.add(new byte[2 * 1024 * 1024]); // 2 MB
Thread.sleep(500);
}
// Start a thread
new Thread(() -> {
while (true) {
try {
Thread.sleep(1000);
System.out.println("Background thread is running...");
} catch (InterruptedException e) {
break;
}
}
}).start();
Thread.sleep(20000); // Keep the application open for monitoring
}
}
Actions:
- Run the program.
- Open VisualVM, find the process, and look at the memory chart and threads.
- Try taking a heap dump and a thread dump.
- For advanced users: launch with JFR and view the results in JMC.
5. Useful tips
Comparison table of monitoring tools
| Tool | What it’s good for | How to run | Notes |
|---|---|---|---|
| JConsole (JMX) | Basic JVM metrics, threads, GC | |
Simple, included in the JDK |
| VisualVM | Monitoring, profiling, heap dump | |
Charts, memory analysis, CPU |
| Java Flight Recorder | Deep analysis of JVM events | |
Analysis in JMC, “black box” |
| JDK Mission Control | Viewing JFR files | separate application | Detailed analytics, reports |
Recommendations
- Monitor not only in production but also during testing. You’ll catch memory leaks and “stuck” threads earlier.
- Heap dumps aren’t scary! Take a memory dump if you suspect a leak and analyze it in VisualVM.
- Don’t be afraid to experiment with JFR. Even if not everything is clear right away, events and charts will help find bottlenecks.
- You can use JMX programmatically. For example, to send metrics to Prometheus/Grafana.
- Profiling isn’t for constant use. Enable profilers selectively; otherwise, the application may slow down.
6. Common mistakes when monitoring the JVM
Error No. 1: Not monitoring at all. Many beginners think that if the application “works,” everything is fine. In reality, memory and thread issues often show up under load or over time. Don’t be lazy — open VisualVM/JConsole at least periodically.
Error No. 2: Taking a heap dump on huge applications without preparation. A heap dump of a large application can be gigabytes in size and “freeze” the process for several seconds. Do this in a test environment or during “quiet” hours if we’re talking about production.
Error No. 3: Ignoring GC and thread metrics. A consistently high share of time spent in GC is a warning sign! And if the number of threads steadily grows — look for a thread leak.
Error No. 4: Not analyzing monitoring results. Just opening VisualVM isn’t enough. Look at which objects consume memory, which methods “eat” CPU, and why deadlocks appear. Examine stack traces and root causes.
Error No. 5: Using profilers in production without understanding. Profiling can significantly slow down the application. Enable it only for diagnostics, not “permanently.”
GO TO FULL VERSION