Monitoring JVM Memory usage inside a Java Application

publish on

How to implement a JVM memory usage threshold warning without hitting the application performance.

We can get the current memory usage of the JVM by using the Runtime.getRuntime().totalMemory() method. So one way to monitor memory consumption would be to create a separate thread that will call this method every second and check if the totalMemory() exceed what we expect. But this will harm the application performance because each call to totalMemoy() method is costly (70 to 100 ns).

A better alternative is to use a platform MXBean, which are a MBean (managed bean) used to monitor and manage the JVM.

here are a list of what can be monitored using an MXBean:

- Number of classes loaded and threads running.
- Java VM uptime, system properties, and VM input arguments.
- Thread state, thread contention statistics, and stack trace of live threads.
- Memory consumption.
- Garbage collection statistics.
- Low memory detection.
- On-demand deadlock detection.
- Operating system information.

So in our case we want to monitor the memory consumption so will use a MemoryMXBean. MemoryMXBean has a neat feature that allows us to set a memory threshold. If the threshold is reached, an event will be fire (so there is no need to check the memory usage periodically).

The first step is to create a class that will catch the event thrown by MemoryMXBean. To do so simply implement the NotificationListener interface and override the handleNotification method. In this method, we log a warning if the notification is of type MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED.

package com.inoneo.util;

import java.lang.management.MemoryNotificationInfo;

import javax.management.Notification;
import javax.management.NotificationListener;


/**
 * Notification call in case of "collection Usage Threshold" reached.
 * 
 * The collection usage threshold is a manageable attribute of some garbage-collected memory pools.
 * After a Java VM has performed garbage collection on a memory pool, some memory in the pool will still be in use.
 * The collection usage threshold allows you to set a value for this memory.
 *
 */
public class LowMemoryListener implements NotificationListener {

    private static final Logger log = Logger.getLogger(LowMemoryListener.class);
    
    public LowMemoryListener() {
        super();
    }

    @Override
    public void handleNotification(Notification notification, Object handback)  {
        String notifType = notification.getType();
        if (notifType.equals(MemoryNotificationInfo.MEMORY_THRESHOLD_EXCEEDED)) {
            // potential low memory, log a warning
            log.warning("Memory usage threshold reached");
        }
    }


}

Now, we need to registered a usage threshold that will trigger the event. The memory is divided into several memory pools. In the code below we loop on each of them and register a threshold for each memory pool that supports this feature.

This piece of code should be place where you want to start to monitor the JVM memory usage, usually at the beginning of the program.

package com.inoneo;

import java.lang.management.ManagementFactory;
import java.lang.management.MemoryMXBean;
import java.lang.management.MemoryPoolMXBean;

import javax.management.NotificationEmitter;

import java.util.List;

import com.inoneo.util.LowMemoryListener;

public static void main(String[] args){

       //Start to monitor memory usage
       MemoryMXBean mbean = ManagementFactory.getMemoryMXBean();
       NotificationEmitter emitter = (NotificationEmitter) mbean;
       LowMemoryListener listener = new LowMemoryListener(this, maxHeapMemoryThreshold);
       emitter.addNotificationListener(listener, null, null);
       
       //set threshold
        List pools = ManagementFactory.getMemoryPoolMXBeans();
        for (MemoryPoolMXBean pool : pools) {
            if(pool.isCollectionUsageThresholdSupported()){
            // JVM Heap memory threshold in bytes (1.00 Gb = 1000000000), 0 to disable
                pool.setUsageThreshold(1000000000);
            }
        }
}

In this example, we set a threshold at 1 Gb of memory, that means an event will be fired if the memory consumption reaches or exceeds 1 Gb. Be careful to set a limit below the max memory configured for the JVM or you'll encounter an exception:

java.lang.IllegalArgumentException: Usage threshold cannot exceed maximum amount of memory for pool.
	at com.ibm.lang.management.MemoryPoolMXBeanImpl.setUsageThreshold(MemoryPoolMXBeanImpl.java:437)

If you want to disable a threshold, set a value to 0.

Resources:
Overview of Java SE Monitoring and Management
Using the Platform MBean Server and Platform MXBeans

comments powered by Disqus