Monitor JVM CPU, Load, and GC Metrics Using java.lang.management
This guide explains how to use Java's java.lang.management APIs to retrieve real-time JVM CPU usage, system load averages, garbage collection details, and heap/non‑heap memory statistics, providing code snippets and practical usage scenarios for performance testing and resource allocation.
Background
When running performance‑testing workloads on a single load‑generator machine, the JVM itself can become a resource bottleneck. Parallel tasks may compete for CPU, memory, or cause excessive garbage‑collection (GC) pauses, which degrades overall test throughput. Accessing JVM and host‑machine metrics at runtime helps detect and mitigate these issues.
Accessing JVM Metrics via java.lang.management.ManagementFactory
The JDK provides a set of MXBean interfaces that expose CPU, memory, and GC statistics of the running JVM. The most frequently used beans are: ThreadMXBean – per‑thread CPU time. OperatingSystemMXBean – OS‑level CPU count, load average, etc. MemoryMXBean – heap and non‑heap memory usage. GarbageCollectorMXBean – GC count and total pause time.
Bean Initialization (static, reused)
static ThreadMXBean threadBean = ManagementFactory.getThreadMXBean();
static OperatingSystemMXBean osMxBean = ManagementFactory.getOperatingSystemMXBean();
static MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
static long lastSysTime = System.nanoTime();
static long lastUserTime = 0L;CPU Usage
The following helper calculates the CPU consumption of the JVM. It can return either the average usage per logical core or the total usage across all cores.
/**
* Number of logical processors available to the JVM.
*/
static int getAvailableProcessors() {
return osMxBean.getAvailableProcessors();
}
/**
* CPU usage as a percentage (0‑100). If <em>avg</em> is true, the value is
* normalized per core; otherwise it is the aggregate usage.
*/
static double getCpuUsage(boolean avg) {
long totalThreadTime = 0L;
for (long id : threadBean.getAllThreadIds()) {
totalThreadTime += threadBean.getThreadCpuTime(id);
}
long now = System.nanoTime();
long used = totalThreadTime - lastUserTime;
long elapsed = now - lastSysTime;
// Update state for next call
lastSysTime = now;
lastUserTime = totalThreadTime;
double usage = (double) used / elapsed * 100.0;
if (avg) {
usage = usage / getAvailableProcessors();
}
// Guard against values > 100 due to measurement jitter
return usage > 100.0 ? 100.0 : usage;
}In containerised environments the value returned by OperatingSystemMXBean.getAvailableProcessors() may differ from the physical host because Docker limits CPU quota. If a custom utility (e.g., com.funtester.utils.OSUtil#getAvailableProcessors) yields unexpected numbers, prefer the standard MXBean method.
System Load Average
Load average represents the average number of runnable threads over a period (1 minute by default). Normalising by CPU count yields a dimensionless metric useful for quick health checks.
/**
* Normalised 1‑minute system load average.
*/
static double getLoad() {
double raw = osMxBean.getSystemLoadAverage();
return raw / getAvailableProcessors();
}Garbage‑Collection Statistics
GC metrics are obtained from the list of GarbageCollectorMXBean instances. Each bean provides the collector name, the number of collections performed, and the cumulative pause time (in milliseconds).
static List<GarbageCollectorMXBean> gcBeans = ManagementFactory.getGarbageCollectorMXBeans();
/**
* Simple DTO that holds GC counters.
*/
static class GCInfo {
String name;
long collectionCount;
long collectionTimeMs;
GCInfo(GarbageCollectorMXBean bean) {
this.name = bean.getName();
this.collectionCount = bean.getCollectionCount();
this.collectionTimeMs = bean.getCollectionTime();
}
}
/**
* Returns a list of GCInfo objects, one per collector.
*/
static List<GCInfo> getGCInfo() {
List<GCInfo> result = new ArrayList<>();
for (GarbageCollectorMXBean bean : gcBeans) {
result.add(new GCInfo(bean));
}
return result;
}GC data is typically used as supplemental information (e.g., to correlate spikes in latency with GC pauses) rather than as a primary scaling signal.
Memory Usage
Heap and non‑heap memory consumption can be queried directly from MemoryMXBean. The returned MemoryUsage objects contain init, used, committed, and max values in bytes.
/**
* Heap memory usage snapshot.
*/
static MemoryUsage heapMemInfo() {
return memoryMXBean.getHeapMemoryUsage();
}
/**
* Non‑heap memory usage snapshot.
*/
static MemoryUsage nonHeapMemInfo() {
return memoryMXBean.getNonHeapMemoryUsage();
}Typical Usage Scenarios
During local performance‑test runs, periodically log the values returned by getCpuUsage, getLoad, heapMemInfo, and getGCInfo alongside test metrics.
In a service‑side load‑testing harness, use the CPU usage percentage as a back‑pressure signal – for example, throttle object‑pool activity or trigger explicit GC when usage exceeds a configurable threshold.
Alternative System‑Information Libraries
If the standard MXBeans do not expose required hardware details (e.g., detailed disk I/O, network statistics, or per‑core temperature), the com.github.oshi:oshi-core:6.4.0 library provides a comprehensive cross‑platform API for system information.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
