JMX Guide

This chapter focuses on JMX itself and provides information about how this technology can be used to monitor and manage Java applications.

The following sections show how basic monitoring and management can be performed starting from pure Java, non-JMX approach. Along the way JMX concepts are introduced and discussed. Eventually we show how Jolokia fits in the JMX architecture.

Java, objects, interfaces, proxies

This section is about the obvious and inherent nature of Java, which is an object oriented language.

When writing Java applications, we always deal with objects, which may be implement interfaces. This is a fundamental aspect of Java. It is so natural that we hardly even think about this.

Object oriented principals state that we invoke methods on objects. The most primitive ways to get these objects in the first place is to instantiate them using new operator. This is straightforward, but we couple our code with some specific implementation.

Using objects directly
java.lang.management.MemoryUsage usage
    = new java.lang.management.MemoryUsage(1024L, 512L, 512L, 2048L);
System.out.println("Memory usage: " + usage.toString());

If our code can get the object in some better way, we reach loose coupling and leave the implementation details to some other mechanism like dependency injection, service locator or other.

The first level of separation is to use an API (interface) and leave the details of how the implementation is created to other parts of the system (JDK, dependency injection framework, …​).

Using objects through interfaces
java.lang.management.MemoryMXBean memory = java.lang.management.ManagementFactory.getMemoryMXBean();
System.out.println("Memory usage: " + memory.getHeapMemoryUsage().toString());

The above example uses the service locator pattern. Our code is aware of the locator (here: java.lang.management.ManagementFactory), but we don’t really know what exactly do we locate - we only know the interface of the actual located service.

Using objects directly and through interfaces is fundamental part of Java language.

How objects, classes and interfaces relate?

Fundamentally, a Java object declares that it can be represented by one or more interfaces using implements statement. This is a feature of the language itself.

However there’s something amazing in the Java Development Kit’s java.lang.reflect package.

While a class (the implementation) and an interface (well …​ the interface) can relate directly and statically, we can have more dynamic relation between an interface (or a set of interfaces) and an implementation:

  • java.lang.reflect.Proxy - a utility to create dynamic proxies

  • java.lang.reflect.InvocationHandler - a generic interface with one method invoke() that can be used to perform or delegate an operation invoked on a proxy.

Using proxies
java.lang.management.MemoryMXBean memory = (java.lang.management.MemoryMXBean) java.lang.reflect.Proxy.newProxyInstance(null, new Class<?>[] { MemoryMXBean.class }, new InvocationHandler() {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        if ("getHeapMemoryUsage".equals(method.getName())) {
            return new MemoryUsage(1024L, 512L, 512L, 2048L);
        }
        throw new UnsupportedOperationException(method.getName());
    }
});
System.out.println("Memory usage: " + memory.getHeapMemoryUsage());

In this example, we still use java.lang.management.MemoryMXBean interface, but this time it is not bound to any real object of some particular class. The implementation behind proxy is dynamic, represented by InvocationHandler.invoke() method.
In this simple example we just create a result directly, but the implementation may be much more sophisticated. For example we can check the the memory usage of a remote JVM by reaching out to another Java application using network (yes - it’s a hint for upcoming JMX and Jolokia sections).

How objects are obtained?

java.lang.management.ManagementFactory.getMemoryMXBean() method is using service locator pattern, where we know the locator (ManagementFactory class) and a way to get particular object of a desired interface (getMemoryMXBean() method).

But again - there’s more. We can get the implementation (the object) injected by some Dependency Injection framework like:

Dependency Injection is a great architectural pattern that allows separation of interfaces and implementations.

Probably more traditional way of getting an implementation for some interface, which falls into a category of service location is through some kind of mapping. Note that Dependency Injection frameworks usually also allow this kind of interaction.

Naming Systems exist to bind objects (services, records, values) to names:

Bound objects can be looked up in the registry using user friendly (or not) identifiers.

There are two important and distinguished kinds of mappings:

  • by name - this is more "traditional" approach where the registry keeps the objects under String keys

  • by interface - this is more modern approach, where the registry keeps the objects under keys which are the interfaces of the bound objects

Spring Framework allows both approaches with these methods respectively:

  • Object org.springframework.beans.factory.BeanFactory.getBean(java.lang.String)

  • <T> T org.springframework.beans.factory.BeanFactory.getBean(java.lang.Class<T>)

More about the registry

Objects (implementation) are represented by interfaces (behavior specifications) and are reachable using keys (identifiers, which may be character strings or interface names).

This idea is a core part of a software component which may be called a registry. Java and other programming languages implement some kind of registry. In Java we have:

  • org.springframework.beans.factory.BeanFactory in Spring Framework

  • jakarta.enterprise.inject.spi.BeanManager in CDI

  • org.osgi.framework.BundleContext in OSGi

  • and yes, javax.management.MBeanServer and com.sun.jmx.mbeanserver.Repository in JMX

Three elements

Before moving to JMX, we can summarize three components of the system we’re trying to describe.

Objects / Services

objects provide some implementation and are accessed using an interface directly (implements) or indirectly (JDK proxies).

The registry

a place where objects/services are stored/registered/bound and made available for others to look up/retrieve.

Metadata

When a registry stores an object/service, it makes it available using some identifier (name or interface). Additionally, each object may be associated with dedicated metadata - either for human user (like description) or other applications (a structural specification of the interface of a given object, perhaps some security/audit requirements, etc.).

It is important to realize that even the name under which a given implementation is available may be considered a part of the metadata.

JMX

Java™ Management Extensions is a dedicated technology used for monitoring and management of Java applications.

JMX uses the concepts described in the Java, objects, interfaces, proxies chapter. In particular:

  • the interface is represented both by methods (operations) we can invoke and attributes we can read or write. There are also notifications which can be emitted.

  • the interface is separated from the implementation. Technically it’s not relevant if the separation is provided using the implements statement from Java language or dynamic proxies. JMX may use both forms of separation. Proxies are required for remote access.

  • the implementation is stored in a registry (javax.management.MBeanServer and com.sun.jmx.mbeanserver.Repository.domainTb)

  • the keys under which an implementation is available are represented by javax.management.ObjectName with JMX-specific syntax.

  • the metadata is represented by javax.management.MBeanInfo.

From programming point of view, JMX consist of:

  • A registry provided by the JDK. We can get access to the registry by obtaining a reference to javax.management.MBeanServer - for example using java.lang.management.ManagementFactory.getPlatformMBeanServer(). Note that we use service locator pattern to obtain an interface which we can use to access an object created somewhere else by something else. Using MBeanServer interface we access other objects by interface - this time by JMX interface. There are more examples of this pattern.

  • A set of MBeans (managed beans) which are bound in the registry under some javax.management.ObjectName in association with their javax.management.MBeanInfo.

  • A series of MBean invocations and attribute access.

Let’s discuss these elements below in more details.

JMX Registry - javax.management.MBeanServer

A service registry is a place where …​ services are registered (and can be looked up from). In Spring Framework, the services are called beans and the registry is named a factory (because the beans are not only stored, but also created there), so it’s called a bean factory.

In JMX, the beans are called MBeans (management beans) and the registry is called the server. That’s why our first abstraction is javax.management.MBeanServer.

Everything in Java (from the point of view of a developer writing applications, not from a perspective of someone who actually writes the JVM itself in C/C++) can be viewed as an object of a class. Such class contains implementation of methods and the object holds some state.

While the JMX registry (MBeanServer) is a way to access the MBeans (more on that later), it also:

  • has some implementation

  • has an interface through which we access it

  • has to be created and obtained in some way

Looking at concepts which don’t match (classes, objects, JMX, MBeans, the registry) at first glance from different perspective is very important skill for understanding complex systems.

How do we obtain a reference to an MBeanServer?

Let’s start from a user perspective. The canonical way to obtain a reference to an MBeanServer is this code:

javax.management.MBeanServer server = ManagementFactory.getPlatformMBeanServer();

With this reference we can use javax.management.MBeanServer API to access the platform MBeanServer. This is the default, built-in, preconfigured JMX registry, where we can find (because Java runtime did it for us) some standard MBeans which we can use without any effort.

How the platform MBeanServer is created?

Because the platform JMX registry is a critical component of entire JMX infrastructure, it is created by the JVM itself, when Java application starts. In most cases, a user should not be responsible for creating it or even for any explicit configuration. The JMX registry should just be there.

Simply calling java.lang.management.ManagementFactory.getPlatformMBeanServer() ensures that the JMX registry is created, but how it is created?

In more details, as with many of the enterprise components of Java, the creation is performed using a factory pattern, where the factory is javax.management.MBeanServerFactory and its method createMBeanServer().
But because it’d be too easy, this single factory uses yet another layer of delegation - it uses a configurable implementation of javax.management.MBeanServerBuilder (it’s a class, but not final, so anyone may provide custom implementation).

Actual implementation of the builder may be specified using -Djavax.management.builder.initial system property and if it’s not available, the builder is javax.management.MBeanServerBuilder.

Here’s the detailed process:

  1. During initialization, java.lang.management.ManagementFactory calls javax.management.MBeanServerFactory.newMBeanServer()

  2. MBeanServerFactory checks -Djavax.management.builder.initial property for a class that can be used as javax.management.MBeanServerBuilder

  3. Whether custom or default javax.management.MBeanServerBuilder is used, it is responsible to create two objects:

    • javax.management.MBeanServerDelegate - management view of javax.management.MBeanServer - providing some information and notification support of the second object created (MBeanServer)

    • javax.management.MBeanServer - the JMX registry and MBean server itself

  4. The created MBeanServer is added to javax.management.MBeanServerFactory.mBeanServerList list for future discovery.

After calling ManagementFactory.getPlatformMBeanServer() these conditions are true:

MBeanServerFactory.findMBeanServer(null).size() == 1;
MBeanServerFactory.findMBeanServer(null).get(0) == ManagementFactory.getPlatformMBeanServer();

And there’s only one available instance of javax.management.MBeanServer available

How to create more more MBeanServers?

Should we? At least we should be able to. Normally it’s good to have just one, central MBeanServer running in a single Java application (JVM process). But it’s not uncommon to have more such servers.

We can simply think about each server as one, isolated, dedicated registry of MBeans we can access (invoke operations, register for notifications, read and write the attributes).

The best way to create more MBeanServers is to do what ManagementFactory.getPlatformMBeanServer() - use javax.management.MBeanServerFactory! There are two methods for this:

  • javax.management.MBeanServerFactory.newMBeanServer(java.lang.String defaultDomain) (if null is passed, default domain becomes DefaultDomain…​) - this method creates an MBeanServer instance using the builder pattern (and possibly -Djavax.management.builder.initial)

  • javax.management.MBeanServerFactory.createMBeanServer(java.lang.String defaultDomain) - calls newMBeanServer() and adds the created MBeanServer to internal list, so we can later find this MBeanServer using its agent it.

Of course we can simply instantiate and implementation of javax.management.MBeanServer and …​ put it somewhere. It’d be good then to document where a user of our application/library can find such custom MBeanServer.

How to find and identify non-platform MBeanServers?

MBeanServers created using javax.management.MBeanServerFactory.createMBeanServer() can be easily found using this Java code:

ArrayList<MBeanServer> list = MBeanServerFactory.findMBeanServer(null);
for (MBeanServer mBeanServer : list) {
    System.out.println(mBeanServer.getAttribute(MBeanServerDelegate.DELEGATE_NAME, "MBeanServerId"));
}

MBeanServer created using other means is not visible to MBeanServerFactory.findMBeanServer, so it’s up to the creator to put it in some known place. Well, we can register it as an MBean in the platform MBeanServer too. That’s what Jolokia is doing in jolokia-support-jmx module - custom MBeanServer is registered under jolokia:type=MBeanServer ObjectName in the platform MBeanServer.

What is agent id passed to MBeanServerFactory.findMBeanServer(String agentId)? If we pass null we’ll get all available MBeanServers. Otherwise we’ll get only the ones with matching agent ID.

As mentioned before, javax.management.MBeanServerBuilder should create the MBeanServer, but also its management view in the form of javax.management.MBeanServerDelegate. By default, for default com.sun.jmx.mbeanserver.JmxMBeanServer implementations, this delegate is com.sun.jmx.mbeanserver.MBeanServerDelegateImpl.

JMX specification defines one (among others) special MBean registered under JMImplementation:type=MBeanServerDelegate ObjectName. This is the management view associated with an MBeanServer.

MBeanServerBuilder uses MBeanServerFactory to create both the delegate and actual server and then the default MBeanServer implementation registers the delegate under JMImplementation:type=MBeanServerDelegate ObjectName in itself.

The delegate (the management view of the MBeanServer) has two purposes:

  • identification - by providing these attributes:

    • MBeanServerId - this is the agent id used by MBeanServerFactory.findMBeanServer()

    • SpecificationName

    • SpecificationVersion

    • SpecificationVendor

    • ImplementationName

    • ImplementationVersion

    • ImplementationVendor

  • notification support for MBean registration

While JMX notifications in general should be discussed in dedicated chapter, JMImplementation:type=MBeanServerDelegate MBean is special, so we have to mention this particular notification type (MBean registration/unregistration) here.

This is an example usage of the delegate:

javax.management.MBeanServer server = ManagementFactory.getPlatformMBeanServer();
server.addNotificationListener(MBeanServerDelegate.DELEGATE_NAME, new NotificationListener() {
    @Override
    public void handleNotification(Notification notification, Object handback) {
        if (notification instanceof MBeanServerNotification serverNotification) {
            if (serverNotification.getType().equals(MBeanServerNotification.REGISTRATION_NOTIFICATION)) {
                System.out.println("MBean registered: " + serverNotification.getMBeanName());
            }
        }
    }
}, null, null);

A client view of an MBeanServer - MBeanServerConnection

This is almost everything we need to know about the JMX Registry. However if we look at the definition of MBeanServer interface, we’ll see this:

public interface MBeanServer extends MBeanServerConnection

MBeanServerConnection and MBeanServer share a lot of methods, but there are important differences:

  • javax.management.MBeanServer represents a local JMX registry (MBean server), so none of the methods throw IOException

  • javax.management.MBeanServerConnection represents a local or remote JMX registry (MBean server), so:

    • all the methods throw IOException

    • there are no instantiate() methods

    • there’s no registerMBean() method, because it operates on a java.lang.Object parameter which is the MBean - actual object.

This is very important. When we describe remote JMX servers, remote connections, security and Jolokia, we’ll use MBeanServerConnection interface more often than MBeanServer emphasizing the remote nature of JMX. And at the same time everything will be valid for local scenarios.

MBeans and metadata

An empty JMX registry (an empty MBeanServer without any registered MBeans) is useless - just like an empty HashMap. javax.management.MBeanServerConnection and javax.management.MBeanServer interfaces (APIs) are important, but the power of JMX is in the MBeans we can access.

Before discussing various ways of registering our own MBeans, lets focus on the ones we can access by default. Simply because any Java application started registers own MBeans.
Instead of simply listing what we can access by default, let’s see it from the JVM perspective.

In How the platform MBeanServer is created? we’ve described how the platform MBeanServer is obtained and created using factory and builder patterns.

java.lang.management.ManagementFactory uses internal java.lang.management.ManagementFactory.PlatformMBeanFinder finder which loads (using Java Service Loader) services of sun.management.spi.PlatformMBeanProvider class. There are 3 such services in standard JDK and each provider is responsible for providing one ore more platform components which are then registered in the MBeanServer being created. Here are the providers and their platform components listed as MBean names (in order of processing and MBean declaration):

  • com.sun.management.internal.PlatformMBeanProviderImpl

    • java.lang:type=GarbageCollector,name=* - different name for each implementation of java.lang.management.GarbageCollectorMXBean

    • java.lang:type=Threading

    • java.lang:type=OperatingSystem

    • com.sun.management:type=HotSpotDiagnostic

    • com.sun.management:type=DiagnosticCommand

  • java.lang.management.DefaultPlatformMBeanProvider

    • java.lang:type=ClassLoading

    • java.lang:type=Compilation

    • java.lang:type=Memory

    • java.lang:type=GarbageCollector,name=* - different name for each java.lang.management.GarbageCollectorMXBean found. Duplication from the previous provider.

    • java.lang:type=MemoryManager,name=* - different name for each java.lang.management.MemoryManagerMXBean found

    • java.lang:type=MemoryPool,name=* - different name for each java.lang.management.MemoryPoolMXBean found

    • java.lang:type=Runtime

    • java.lang:type=Threading - Duplication from the previous provider. However This MBean is sun.management.ThreadImpl, while the one from the previous provider is an extension - com.sun.management.internal.HotSpotThreadImpl

    • java.util.logging:type=Logging

    • java.nio:type=BufferPool,name=* - different name for each java.lang.management.BufferPoolMXBean found

    • java.lang:type=OperatingSystem - Duplication from the previous provider. Here it’s sun.management.BaseOperatingSystemImpl, previous provider registers an extension - com.sun.management.internal.OperatingSystemImpl

  • jdk.management.jfr.internal.FlightRecorderMXBeanProvider

    • jdk.management.jfr:type=FlightRecorder

Each such provider contains some platform components and if more providers include the same MBeans, the first one is registered. In the above list it means that these are taken from com.sun.management.internal.PlatformMBeanProviderImpl, not from java.lang.management.DefaultPlatformMBeanProvider:

  • java.lang:type=GarbageCollector,name=*

  • java.lang:type=Threading

  • java.lang:type=OperatingSystem

With just the default MBeans we can use JMX technology to monitor any Java application by checking the most important information about threads, memory, CPU usage etc. However we can easily register our own MBeans to provide as much information and control as we want.

JMX wouldn’t be considered an enterprise feature if it was limited to built-in MBeans. We can register our own MBeans using this simple call:

javax.management.MBeanServer server = ManagementFactory.getPlatformMBeanServer();\
server.registerMBean(new MyMBean(), javax.management.ObjectName.getInstance("com.example:name=mymbean"));

The first argument of javax.management.MBeanServer.registerMBean() method is a java.lang.Object, so from Java perspective it can be …​ anything.

But from JMX perspective we can register only an MBean that is compliant. Otherwise we’ll get a javax.management.NotCompliantMBeanException.

This is an inherent aspect of the enterprise programming and actually even of human social interactions - by following some rules we ensure better interaction and predictable/expected behavior. The following subsections of this MBeans and metadata chapter present conventions defined directly in the JMX specification.

What is the management interface?

Quoting chapter 1.4.1.1 Managed Beans (MBeans) of the JMX specification:

An MBean is a Java object that implements a specific interface and conforms to certain design patterns. These requirements formalize the representation of the resource’s management interface in the MBean. The management interface of a resource is the set of all necessary information and controls that a management application needs to operate on the resource.

Let’s dissect this definition and explain all the terms that require clarification.

a Java object that implements a specific interface

this is pure Java language requirement. We have an object which is an instance of some class which implements some interface.

management interface

a combination of attributes we can read and/or write, operations we can invoke and notification we can observe. Sure - Java language interface may be used to specify such management interface, but it is not a 1:1 requirement. The management interface may be perceived as an API for some well defined aspect of management and monitoring.

resource

the implementation of the management interface. It can be a well defined Java class implementing an interface, but it doesn’t have to. The resource is a component that fulfills the management interface and is accessed via this management interface through and MBeanServer or generally through an MBeanServerConnection.

a management application

a component that needs to manage/monitor a Java application. Imagine a browser-based dashboard HTML page were we can see a chart of memory usage of a Java application. It needs to know where the monitored application runs (this is the remote aspect, see later), but more importantly it needs to know how to get the required information. In JMX terms - which attributes should be read or which operations should be invoked.

set of all necessary information and controls

as programmers, we have various sources to learn about what can we do with JDK. The best way to know what are the methods provided by some Java interface is to read the source code. But it’s not that easy from automation point of view. Just as Java language and JDK gives us the Reflection API, JMX allows us (and the management applications) to discover what can we do with the management interface of an MBean. We can also check all the available MBeans in the first place. Such information can be used to dynamically build user interfaces without any hard-coded information.

Hopefully it is now clear that the object we register using the javax.management.MBeanServer.registerMBean() method needs to be somehow associated with a proper management interface.
The JMX specification precisely defines 4 types of MBeans we can register and we’ll discuss each type in the following sections.

Looking from user (who registers the MBean using the javax.management.MBeanServer.registerMBean() method) There are exactly two kinds of MBeans:

Static MBeans

A Java object of a class implementing a Java interface. In this Java interface each JMX attribute is represented as a getter/setter Java method and each JMX operation is represented by a non-attribute Java method.

Dynamic MBeans

A Java object of a class implementing (directly or indirectly) the javax.management.DynamicMBean interface. Here each JMX attribute is handled dynamically by setAttribute()/setAttributes()/getAttribute()/getAttributes() implementation and each JMX operation is handled dynamically in an implementation of the generic invoke() method.

JMX notifications are orthogonal to the above categories and are handled by implementing the javax.management.NotificationEmitter interface - whether the MBean is static or dynamic.
Whether the registered MBean is static or dynamic it is internally (by the default JMX implementation) stored as a dynamic MBean. Simply static MBeans are analyzed using Java reflection and wrapped using com.sun.jmx.mbeanserver.Introspector.makeDynamicMBean() as dynamic MBeans.

What is the metadata?

Whatever kind (static, dynamic) and type (standard, dynamic, open, model) of the MBean we use, the JMX registry needs an MBean metadata that describes an MBean.

Similarly to Java Reflection API, JMX describes an MBean using one of the interfaces specific to a type of MBean. We will describe the metadata in relevant sections, but here’s the list:

  • javax.management.MBeanInfo - the basic (and usually sufficient for all kinds of MBeans) metadata describing the attributes, operations, notifications, constructors and a description of an MBean. It is a class, not an interface.

  • javax.management.openmbean.OpenMBeanInfo - this is a metadata specific for Open MBeans. It was supposed to extend javax.management.MBeanInfo which was supposed to be changed into an interface long time ago…​

  • javax.management.modelmbean.ModelMBeanInfo - this is a metadata specific for Model MBeans.

Standard MBeans

Standard MBeans adhere to a simple convention which dates back to Java Beans™ specification:

  • We need a class that implements a Java interface. This interface defines the management interface of the standard MBean.

  • Each method of the interface which matches get*/is* name becomes a readable JMX attribute

  • Each method of the interface which matches set* name becomes a writable JMX attribute (usually together with a matching getter)

  • Other methods of the interface become a JMX operation

The last requirement is that the interface being implemented by the class has to be named after the class name with special suffix. And for standard MBeans we have exactly two such suffices:

  1. MBean (for example class MyService implements MyServiceMBean) - this is the standard standard MBean with absolutely no restriction on the types of attributes, operation parameters and return types.

  2. MXBean (for example class MyService implements MyServiceMXBean) - this is a special version of a standard MBean called "MX Bean" where we can use only the types specified for Open MBeans type of dynamic MBeans.

Additionally option 2 can be chosen by annotating the interface (with any name) using @javax.management.MXBean annotation.

Here’s an example of a standard MBean interface. If we create a class that implements such interface, we can pass an object of this class to the javax.management.MBeanServer.registerMBean() method.

public interface MyServiceMBean {
    String getMessage();

    String hello();
}

This interface defines one read-only attribute named "Message" and one operation named "hello". Now we only need a class in the same package as this interface and named MyService to be able to register an MBean.

By constraining ourselves to the Open Types only we can define an special variant of an MBean called the "MX Bean". The advantage is that (after allowing remote access) such MBean can be accessed not only by Java applications, but all other types of clients which only have to be aware of precisely defined set of data types.

Open MBeans sections provides more information about the open set of Java types which can be used with MX Beans. Full "MX Bean" specification is available in @MXBean Javadoc.

@MXBean Javadoc provides much more information about MX Beans than the JMX specification itself.

The nice thing about static MBeans (or MX Beans) is that we don’t have to build/implement dedicated metadata (javax.management.MBeanInfo) - we’ll get it for free during registration time thanks to com.sun.jmx.mbeanserver.MBeanIntrospector.

Dynamic MBeans

As mentioned in the How objects, classes and interfaces relate? section we have two kinds of relation between an implementation and interface in Java:

  • class AnImplementation implements AnInterface - static declaration that a class implements an interface

  • java.lang.reflect.Proxy and java.lang.reflect.InvocationHandler - dynamic association of an interface with implementation

We have the same concept materialized in JMX. Standard (static) MBeans use a class which implements a Java interface (with special suffix or annotation though).
Dynamic MBeans are classes that implement javax.management.DynamicMBean interface, where each attribute and each operation do not require dedicated Java interface method.

  • all JMX attributes are handled by generic javax.management.DynamicMBean.getAttribute()/getAttributes()/setAttribute()/setAttributes() methods

  • all JMX operations are handled by a generic javax.management.DynamicMBean.invoke() method.

  • the JMX metadata is provided by javax.management.DynamicMBean.getMBeanInfo() method.

That’s it. Dynamic MBeans can be used to dynamically implement JMX attributes and JMX operations instead of defining dedicated getters/setters/methods.

It may be surprising to learn that javax.management.StandardMBean interface is not a standard MBean as shown in the Standard MBeans section. Instances of javax.management.StandardMBean are dynamic MBeans that are constructed by performing an introspection of a class that implements some interface which doesn’t have to follow the *MBean / *MXBean naming convention.

By extending javax.management.StandardMBean we can override some behavior and alter the returned metadata, thus getting more flexibility (it’s still a dynamic MBean) and keeping some simplicity (the reflection based approach is still applied internally by com.sun.jmx.mbeanserver.MBeanIntrospector).

Open MBeans

Open MBeans are specialized (constrained) dynamic MBeans which use only predetermined set of Java types.

The term open means that the MBeans following this convention are more open to the wider range of management applications. It means we can access them not only from Java applications, but other applications as well - provided that the strict set of data types is supported.

There’s no dedicated javax.management.DynamicMBean extension for open MBeans.

However there is a dedicated JMX metadata interface for open MBeans: javax.management.openmbean.OpenMBeanInfo.
If our dynamic MBean implements javax.management.DynamicMBean.getMBeanInfo() to return javax.management.openmbean.OpenMBeanInfoSupport instead, the MBean is treated as open MBean.

JMX Specification defines a very strict set of supported types which can be used for JMX attributes and JMX operation parameters and return values. All the types fall into 4 categories which have their Java representation in the form of javax.management.openmbean.OpenType class.
Here’s the list of categories and related types:

  • javax.management.openmbean.SimpleType

    • 8 primitive and wrapper types (byte, short, int, long, float, double, char, boolean)

    • java.lang.String

    • java.math.BigInteger

    • java.math.BigDecimal

    • java.util.Date

    • javax.management.ObjectName

    • java.lang.Void

  • javax.management.openmbean.ArrayType - single and multidimensional arrays of all 4 categories of open types

  • javax.management.openmbean.CompositeType - types that can represents maps or more generally the key-value pairs. A composite type requires a specification of all items it can contain, where each item has a specified name and a type of the value. The value type may be any of the 4 categories of open types allowing for great flexibility.

  • javax.management.openmbean.TabularType - the best analogy would be a database table containing rows of data. Each such row is of the single defined CompositeType, but additionally the TabularType defines a subset of items of the row’s CompositeType which build an index. There can’t be no rows available with the same index (the same values for each of the items of the defined index).

Standard MBeans sections mentioned that "MX Beans" follow the type rules for Open Types. See @MXBean Javadoc for full MX Beans specification.
In particular this specification mentions that the types that can be used for MXBeans may be the types that are specified for Open MBeans, but also types which are convertible to and reconstructible from the open types.

For example there are rules to convert java.util.Map objects into javax.management.openmbean.TabularType and any bean classes into javax.management.openmbean.CompositeType.

Model MBeans

Well, the final boss of all MBean types…​

Let’s see the progression:

  • standard MBeans (static) have the _management interface hardcoded as Java interface methods and the metadata is generated by reflection

  • dynamic MBeans (including open MBeans with restricted types) make the implementation dynamic (setAttribute(), invoke()) but the metadata (MBeanInfo) is returned from the same class (implementing javax.management.DynamicMBean)

  • model MBeans separate the implementation and metadata. And put emphasis on the metadata…​

How does it work? Well, let’s assume we have a class written in mid 1990s, which doesn’t implement any interface and there are no getters. However we want to use it as managed bean (MBean) accessible using JMX.

For example we have this trivial class:

public static class ServerStats {
    private int activeConnections = 5;

    public int connections() {
        return activeConnections;
    }

    public void changeConnections(int value) {
        this.activeConnections = value;
    }
}

If we can’t (afford to) refactor this class and we want to register it as an MBean with one "ActiveConnections" attribute (read/write), we can craft special metadata (in the form of javax.management.modelmbean.ModelMBeanInfo) which tells JMX everything that describes our MBean.

Here’s an example:

Descriptor activeConnectionsDesc = new DescriptorSupport(new String[] {
        "name=ActiveConnections",
        "descriptorType=attribute",
        "getMethod=connections",
        "setMethod=changeConnections",
});
ModelMBeanAttributeInfo activeConnectionsAttr =
        new ModelMBeanAttributeInfo(
                "ActiveConnections",
                "int",
                "Number of currently active connections",
                true,   // readable
                true,   // writable
                false,  // isIs
                activeConnectionsDesc
        );

Descriptor connectionsDesc = new DescriptorSupport(new String[] {
        "name=connections",
        "descriptorType=operation",
        "role=getter",
        "attribute=ActiveConnections"
});
Descriptor changeConnectionsDesc = new DescriptorSupport(new String[] {
        "name=changeConnections",
        "descriptorType=operation",
        "role=setter",
        "attribute=ActiveConnections"
});

ModelMBeanOperationInfo connectionsOperation =
        new ModelMBeanOperationInfo(
                "connections",
                "Returns the number of active connections",
                null,
                "int",
                ModelMBeanOperationInfo.INFO
        );

MBeanParameterInfo[] setConnParams = {
        new MBeanParameterInfo(
                "value",
                "int",
                "New number of active connections"
        )
};

ModelMBeanOperationInfo changeConnectionsOp =
        new ModelMBeanOperationInfo(
                "changeConnections",
                "Sets the number of active connections",
                setConnParams,
                "void",
                ModelMBeanOperationInfo.ACTION
        );

ModelMBeanInfo modelMBeanInfo =
        new ModelMBeanInfoSupport(
                ServerStats.class.getName(),
                "ModelMBean exposing ServerStats via non-standard methods",
                new ModelMBeanAttributeInfo[] {
                        activeConnectionsAttr
                },
                null, // constructors
                new ModelMBeanOperationInfo[] {
                        connectionsOperation,
                        changeConnectionsOp
                },
                null  // notifications
        );

ServerStats resource = new ServerStats();

RequiredModelMBean modelMBean = new RequiredModelMBean(modelMBeanInfo);
modelMBean.setManagedResource(resource, "ObjectReference");

MBeanServer mbs = ManagementFactory.getPlatformMBeanServer();

ObjectName name = new ObjectName("example.jmx:type=ServerStatsModelMBean");
mbs.registerMBean(modelMBean, name);

mbs.setAttribute(name, new Attribute("ActiveConnections", 42));
Object connections = mbs.getAttribute(name, "ActiveConnections");
System.out.println(connections);

A lot of hassle, but shows how powerful and complex model MBeans are. We can think about this approach as:

Declaratively controlled Reflection-based introspection

The declaration is very verbose, but for complex systems it may be justified to externalize such declarations in XML/JSON/YAML formats to support Java classes that can’t be turned into MBeans in an easier way.

Accessing MBeans

With the background provided earlier, we now have this simple summary:

  • A JMX Registry (MBeanServer) for registration and accessing MBeans

  • MBeans of different types from which we can choose according to requirements

  • MBean metadata that describe the MBeans and can either be generated (by Reflection), provided (by implementing javax.management.DynamicMBean.getMBeanInfo()) explicitly in code or provided declaratively for model MBeans

  • management interfaces that describe what can we do with the MBeans (access attributes, invoke operations, react to notifications)

Before moving to the remote aspects of JMX let’s finish this section by showing how to access the MBeans.

There are two ways - fully generic and more Java language friendly.

Generic access to MBeans

Generic access means the MBeans are accessed in the same way whatever their management interface is. We simply use MBeanServer or MBeanServerConnection API.

Here’s an example of getting some attributes for built in MBean related to memory:

MBeanServer server = ManagementFactory.getPlatformMBeanServer();

CompositeData usage = (CompositeData) server.getAttribute(ObjectName.getInstance("java.lang:type=Memory"), "HeapMemoryUsage");
System.out.println("Used memory: " + usage.get("used"));

javax.management.openmbean.CompositeData is one of the supported open types. If we look at the MBeanInfo of java.lang:type=Memory MBean and MBeanAttributeInfo for its `HeapMemoryUsage attribute, we can see the definition (formatted for clarity):

javax.management.openmbean.OpenMBeanAttributeInfoSupport(
    name=HeapMemoryUsage,
    openType=javax.management.openmbean.CompositeType(
        name=java.lang.management.MemoryUsage,
        items=(
            (
                itemName=committed,
                itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)
            ),
            (
                itemName=init,
                itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)
            ),
            (
                itemName=max,
                itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)
            ),
            (
                itemName=used,
                itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)
            )
        )
    ),
    default=null,
    minValue=null,
    maxValue=null,
    legalValues=null,
    descriptor={
        openType=javax.management.openmbean.CompositeType(
            name=java.lang.management.MemoryUsage,
            items=(
                (
                    itemName=committed,
                    itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)
                ),
                (
                    itemName=init,
                    itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)
                ),
                (
                    itemName=max,
                    itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)
                ),
                (
                    itemName=used,
                    itemType=javax.management.openmbean.SimpleType(name=java.lang.Long)
                )
            )
        ),
        originalType=java.lang.management.MemoryUsage
    }
)

As mentioned in Open MBeans section, this is how various complex types are mapped into a strict set of supported open types.

This is also a reason why Jolokia can handle various MBeans in generic way - simply by using a generic javax.management.MBeanServer[Connection] API.

But there’s more.

Access to MBeans using JMX proxies

Standard MBeans section describe MBeans and MXBeans where a registered MBean needs to implement (using implements clause) an interface with MBean or MXBean suffix.
Wouldn’t it be nice if we could access the MBeans using these interfaces? After all we started this chapter with Java, objects, interfaces, proxies section - perhaps we could create a java.lang.reflect.Proxy with an interface like java.lang.management.MemoryMXBean and a java.lang.reflect.InvocationHandler that transparently turns the method argument into a call to javax.management.MBeanServer[Connection]?

Well, javax.management.JMX utility does exactly that! The above scenario is implemented by two methods:

  • javax.management.JMX.newMBeanProxy()

  • javax.management.JMX.newMXBeanProxy()

And here’s how we can use them:

MBeanServer server = ManagementFactory.getPlatformMBeanServer();

MemoryMXBean bean = JMX.newMXBeanProxy(server, ObjectName.getInstance("java.lang:type=Memory"), MemoryMXBean.class);
MemoryUsage usage = bean.getHeapMemoryUsage();
System.out.println("Used memory: " + usage.getUsed());

Isn’t it nice? And what’s more, instead of getting javax.management.openmbean.CompositeData for HeapMemoryUsage attribute, we get java.lang.management.MemoryUsage object directly! And JMX proxy performs the open type conversion for us.

But there’s more. At least for _platform MBeans.

Access to platform MBeans

We’ve already used ManagementFactory.getPlatformMBeanServer() to get access to the platform MBeanServer. The same static class can be used to access some platform MBeans directly! Here are the methods:

MemoryMXBean memoryMXBean = ManagementFactory.getMemoryMXBean();
System.out.println("Used memory: " + memoryMXBean.getHeapMemoryUsage().getUsed());

List<MemoryManagerMXBean> memoryManagerMXBeans = ManagementFactory.getMemoryManagerMXBeans();

ClassLoadingMXBean classLoadingMXBean = ManagementFactory.getClassLoadingMXBean();
System.out.println("Total loaded class count: " + classLoadingMXBean.getTotalLoadedClassCount());

CompilationMXBean compilationMXBean = ManagementFactory.getCompilationMXBean();

List<GarbageCollectorMXBean> garbageCollectorMXBeans = ManagementFactory.getGarbageCollectorMXBeans();

List<MemoryPoolMXBean> memoryPoolMXBeans = ManagementFactory.getMemoryPoolMXBeans();

OperatingSystemMXBean operatingSystemMXBean = ManagementFactory.getOperatingSystemMXBean();
System.out.println(operatingSystemMXBean.getName() + " / " + operatingSystemMXBean.getVersion());

RuntimeMXBean runtimeMXBean = ManagementFactory.getRuntimeMXBean();
System.out.println(runtimeMXBean.getVmVersion());

ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();

The returned references are not proxies - these are references to actual objects registered in JMX. So for platform MBean access we can skip the reflective invocation.

Summary

This chapter presented a big chunk of the original JMX Specification and in particular:

  • Entire chapter I JMX Instrumentation Specification.

  • Most of the chapter II JMX Agent Specification without some parts which either do not need separate explanation or are a bit less relevant from Jolokia and JMX user point of view. Like:

    • details about some helper classes like javax.management.ObjectInstance

    • some aspects of MBean registration like javax.management.MBeanRegistration interface

    • queries

    • M-Lets (management applets) - this is now much less relevant topic, as it covers dynamic and remote class loading which is most of the time a security issue. After all we don’t want to make it easy to download such XML file from a remote location:

      <MLET CODE=object6 ARCHIVE=mybean.jar NAME="object6">
      </MLET>
    • javax.management.monitor package and JMX monitoring

    • javax.management.timer package and JMX timers

    • javax.management.relation package and JMX relations

    • non-remote JMX security, as this chapter is based on java.lang.SecurityManager which has been removed with JEP 486 in JDK 24

Chapter "5.3. Protocol Adaptors and Connectors" will be presented in JMX Remote Guide chapter.

As we can see, there’s no mention of Remote JMX which was originally part of JSR 160: Java™ Management Extensions (JMX) Remote API specification, but which is now part of JSR 3: Java™ Management Extensions (JMX™) Specification.

Where does Jolokia fit in? Jolokia shines in the remote features of JMX and we have a dedicated chapter for this. See JMX Remote Guide.

However there are few features Jolokia provides for the non-remote part of JMX:

  • JMX Support - shows how Jolokia deals with multiple instances of javax.management.MBeanServer and what are @JsonMBeans

  • Jolokia MBeans - shows MBeans which Jolokia itself registers into the Platform MBean Server.

See the JMX Remote Guide chapter where we discuss JSR 160: Java™ Management Extensions (JMX) Remote API and how Jolokia makes it much more approachable.

This page was built using the Antora default UI. The source code for this UI is licensed under the terms of the MPL-2.0 license. | Copyright © 2010 - 2023 Roland Huß