Monitoring Thread Pool in Java with MBeans

Esta entrada de blog también está disponible en Español

Working with Java applications that use native thread pools, one of the limitations we may encounter is the lack of built-in tools to monitor the behavior of these pools. Although libraries such as ThreadPoolExecutor in Java offer powerful functionalities to manage threads, they do not directly expose metrics for monitoring tools such as JMX.

In this blog, I share how to leverage MBeans to expose custom thread pool metrics. This will allow us to monitor in real time aspects such as the number of active threads, the size of the pool, and the size of the task queue for them.


Why use MBeans?

MBeans (Managed Beans) are a mechanism built into Java (standard) for exposing information about the behavior and state of an application. Using MBeans has several advantages:

  • Real-Time Monitoring: We can check the metrics from tools such as JConsole, VisualVM, Glowroot, Dynatrace, etc.

  • Flexibility: It allows us to expose metrics from any component that does not have native support for it.

  • Interoperability: It integrates with advanced monitoring systems such as Prometheus or Grafana, which are a standard in the Cloud paradigm.


Implementing MBeans for Thread Pools

Let's build an example where we register an MBean that monitors a ThreadPoolExecutor. This MBean will expose three key metrics:

  • Active Threads: Number of parallel threads currently executing tasks.

  • Pool Size: Total number of threads in the pool.

  • Queue Length: Number of tasks waiting in the queue.

Code

The following code snippet implements the registration of an MBean to monitor the thread pool in a portlet:

    private static final String MBEAN_NAME = "com.liferay.mcv.example.parallel:type=ThreadPool,name=ParallelThreadsPool";

    @Override
    public void init() throws PortletException {
        super.init();
        registerMBean();
    }

    @Override
    public void destroy() {
        super.destroy();
        executorService.shutdown();
    }

    private void registerMBean() {
        try {
            MBeanServer mBeanServer = ManagementFactory.getPlatformMBeanServer();
            ObjectName objectName = new ObjectName(MBEAN_NAME);
            ThreadPoolMetrics metrics = new ThreadPoolMetrics(executorService);
            if (!mBeanServer.isRegistered(objectName)) {
                mBeanServer.registerMBean(metrics, objectName);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public static class ThreadPoolMetrics implements ThreadPoolMetricsMBean {
        private final ThreadPoolExecutor executor;

        public ThreadPoolMetrics(ThreadPoolExecutor executor) {
            this.executor = executor;
        }

        @Override
        public int getActiveThreads() {
            return executor.getActiveCount();
        }

        @Override
        public int getPoolSize() {
            return executor.getPoolSize();
        }

        @Override
        public int getQueueSize() {
            return executor.getQueue().size();
        }
    }

    public interface ThreadPoolMetricsMBean {
        int getActiveThreads();
        int getPoolSize();
        int getQueueSize();
    }
}

Code Analysis

  1. Registering the MBean: The registerMBean method uses the JVM's MBeanServer to register an object that exposes thread pool metrics. This allows monitoring tools to access these metrics.

  2. MBean Interface: The ThreadPoolMetricsMBean interface defines the methods that the MBean will expose. Each method corresponds to a specific metric.

  3. Exposed Metrics:

    • getActiveThreads: Returns the number of threads currently running.

    • getPoolSize: Returns the total size of the pool.

    • getQueueSize: Returns the number of pending tasks in the queue.

In the following GitHub repository you can find the complete example with which you can deploy the example portlet in Liferay DXP 7.4 Q4.0:

https://github.com/marcialcv/sample-workspace-7.4/blob/mbean-parallel-threads/modules/custom-portlet/src/main/java/com/liferay/mcv/example/parallel/CustomPortlet.java 

 

Checking the metrics

In the portlet example, I implement an action to add 10 threads with each invocation, which print a message on the screen for 30 seconds. A maximum of 100 threads will be allowed to be added and from there, they will be queued up to a maximum of 200.

Once the example is explained, after registering the MBean (during the Portlet deployment), we can use tools such as Glowroot or VisualVM to inspect its metrics by doing the following steps:

  1. We connect to the JVM where our Java application is running (like Liferay DXP or your SpringBoot app ;) )

  2. View the "MBeans" and locate the MBean under the name: com.liferay.mcv.example.parallel:type=ThreadPool,name=ParallelThreadsPool
    (With Glowroot it will be necessary to add the MBean to the configuration in gauges previously, from the Console)

  3. Playing with the portlet action, you can view the metrics displayed in real time and create alerts based on this metrics:


 

Conclusion

Implementing MBeans to monitor thread pools is an elegant and efficient solution when working with mechanisms that do not expose metrics by default. This technique is not only useful for thread pools, but also for any shared resource that needs to be monitored.

With this solution, you can improve the visibility of your applications and anticipate potential bottlenecks, taking the monitoring of your systems to a new level.