This document describes best practices that have been identified for modeling using the JMX API. The suggestions here are not carved in stone, but if you find that they are inappropriate for your modeling effort, please let us know so that we can discuss your situation and if necessary update this document. Some of the conventions described here are inconsistent with parts of the JMX API or of JSR 77 (J2EE management). They represent lessons learnt since those APIs were defined. Although the APIs cannot be changed to conform to the conventions here, these conventions should probably be followed in future additions to the APIs.
Every JMX MBean has an Object Name. It is very important to choose consistent and usable Object Names as described here. Bear in mind that the client interacting with your model might be showing these Object Names directly to a human (for example, it is a JMX-aware console); or it might be a program that is accessing MBeans in your model whose names you have clearly defined. Also, of course, MBeans should be named consistently across the model and, as far as possible, between models.
An Object Name is an instance of javax.management.ObjectName
.
An Object Name is either the name of an MBean, or it is a pattern, potentially matching the names of many MBeans.
It looks like this:
domain: key-property-list
For example:
com.sun.someapp:type=Whatsit,name=25
Here, domain is an arbitrary string.
If the domain is empty, it implies the default domain for the MBean Server where the Object Name is used.
If the domain contains at least one *
or ?
character then the Object Name is a pattern. These characters cannot be used in the name of an MBean, only when matching MBeans.
The domain cannot contain the colon character ( :
).
The key-property-list in an MBean name contains one or more key properties. A key property looks like name= value, for example type=Thread
. When there is more than one key property, they are separated with commas, for example type=Thread,name=DGC
.
Spaces are significant everywhere in an Object Name. Do not write type=Thread, name=DGC
(with a space after the comma) because it will be interpreted as having a key called " name
", with a leading space in the name.
Object Names are case sensitive.
The order of key properties is not significant. There is no difference between type=Thread,name=DGC
and name=DGC,type=Thread
.
The set of characters in a key is limited. It is recommended to stick to legal Java identifiers.
The set of characters in a value is also limited. If special characters may occur, it is recommended that the value be quoted, using ObjectName.quote. If the value for a given key is sometimes quoted, then it should always be quoted. By default, if a value is a string (rather than a number, say), then it should be quoted unless you are sure that it will never contain special characters. Here are some examples of Object Names containing quoted values:
com.sun.someapp:type=Whatsit,name="25"
com.sun.someapp:type=Whatsit,name="25,26"
The second example would be illegal without quoting.
In a pattern, the key-property-list can have the same form as just described; or it can be a single *
; or it can be empty (equivalent to a *
); or it can be a list followed by ,*
. These forms match Object Names that have the exact key properties given (if any) plus any arbitrary other key properties. For example, *:type=Thread,*
matches somedomain:type=Thread
and somedomain:type=Thread,name=DGC
.
Object Names should be predictable. This means that if you know that there is going to be an MBean representing a certain object, then you should be able to know what its name will be. The name should not contain properties that are not an inherent part of the object's identity. The name of the same object should not change between different executions of the application, so for example it should not contain items like a "JVM id" or the "MBean Server Id" that will change with each execution. (The concept of "the same object" is not always clear or meaningful, but when it is, the same object should always have the same name.)
The domain part of an Object Name should start with a Java package name. This prevents collisions between MBeans coming from different subsystems. There might be additional text after the package name. Examples:
com.sun.someapp:type=Whatsit,name=5
com.sun.appserv.Domain1:type=Whatever
The domain should not contain the character slash ( /
). That character is reserved for MBean Server hierarchies ("cascading"). This will be detailed further in a later version of this document.
Every Object Name should contain a type=
key property. This property should be different for every object type in a given domain. JSR 77 (J2EE management) defines a j2eeType=
key property with essentially the same semantics. Since JSR 77 allows arbitrary extra keys in addition to the standard ones it defines, it is possible to add a type=
key in addition to the j2eeType=
one. Note that allowing extra keys like this contradicts the predictability requirement stated above and is not recommended for new JMX models. It makes writing non-interactive clients unnecessarily difficult (you always have to query before accessing any object to discover its actual name).
Every Object Name with a given type
should have the same set of key properties with the same syntax and semantics for their values.
If there can only be one instance of a given type in a given domain, there should not usually be any other key properties than type
.
If there can be several instances of a given type, they can be differentiated either by being in different domains or by further key properties. Most often, domains are relatively fixed, and variable parts of names are in key properties.
The most usual key property in addition to type
is name
. Often, type=X,name=Y
is enough to name an MBean. Some JMX-aware consoles are able to display this form of name using a shorthand that is easier to read than other names.
Sometimes it is useful to define additional properties as well as name
, to facilitate searches using patterns as described above. For example, there might be a category
or group
property, allowing patterns such as:
com.sun.someapp:group=configuration,*
Sometimes, there are managed objects that are logically contained in other managed objects. An object is logically contained in another if it cannot exist independently. In this case, the following scheme is often appropriate. Suppose you have Server objects which contain Application objects which contain WebModule objects which contain Servlet objects. Then their names would look like this:
domain:type=Server,name=server5
domain:type=Server.Application,Server=server5,name=app1
domain:type=Server.Application.WebModule,Server=server5,
Application=app1,name=module3
domain:type=Server.Application.WebModule.Servlet,Server=server5,
Application=app1,WebModule=module3,name=default
The hierachical type
property makes it possible to understand the meaning of the other keys without a priori knowledge of the model. Since Object Name keys are unordered, it would otherwise not be possible to know whether for instance, in the third and fourth names here, an Application is contained in a Server or vice versa.
This scheme implies that if an object of a given type is sometimes contained in an object of another type, then it is always so contained. So here, for example, a Server.Application is always contained in a Server. If there are other Application objects that are not contained in Server objects, then they are of a different type, and that is reflected in the fact that their type
property will not be Server.Application
. This is consistent with the rule that a given type
always implies the same list of other key properties.
Most MBeans are specified using Standard MBean interfaces such as this:
public interface CacheControlMBean {
int getSize() throws IOException;
void setSize(int size) throws IOException;
int getUsage() throws IOException;
int dropOldest(int n) throws IOException;
}
It is strongly recommended that MBeans be specified in this way where possible. This allows them to be documented using the familiar Javadoc tool, and it allows client code to interact with them straightforwardly via proxies, using MBeanServerInvocationHandler
. Contrast the code without a proxy:
MBeanServer mbs = ...;
Integer sizeI = (Integer) mbs.getAttribute(objectName, "Size");
int size = sizeI.intValue();
if (size > desiredSize) {
mbs.invoke(objectName,
"dropOldest",
new Integer[] {new Integer(size - desiredSize)},
new String[] {"int"});
}
with the code that uses a proxy:
MBeanServer mbs = ...;
CacheControlMBean cacheControl = (CacheControlMBean)
MBeanServerInvocationHandler.newProxyInstance(mbs,
objectName,
CacheControlMBean.class,
false);
int size = cacheControl.getSize();
if (size > desiredSize)
cacheControl.dropOldest(size - desiredSize);
The creation of the proxy is somewhat verbose, but once it is available, the MBean can be accessed like a local object. This is much easier to write and read, and much less error-prone, than accessing the MBeanServer
method directly.
It is good practice for every method in a Standard MBean interface to be declared to throw java.io.IOException
as shown here. This forces the code using the proxy to handle this exception explicitly. Otherwise, a communication problem while using the proxy will show up as the unchecked exception UndeclaredThrowableException
wrapping the original IOException
. If your MBean will only ever be accessed locally (from within the same Java Virtual Machine) then declaring IOException
is not necessary, but this case is rare. One way to simplify local access is just to declare a subinterface that redeclares all the same methods but without IOException
:
public interface LocalCacheControlMBean extends CacheControlMBean {
int getSize();
void setSize(int size);
int getUsage();
int dropOldest(int n);
}
This is essentially the approach adopted in the JMX API itself, between the remote interface MBeanServerConnection
and the local interface MBeanServer
. The Java class that implements the interface can implement LocalCacheControlMBean
rather than CacheControlMBean
- this will help prevent having the two interfaces get out of sync.
Occasionally, Standard MBeans are not enough. Dynamic MBeans should be used when the interface of a managed object is not known at compile time. For example, you might have an MBean that represents an XML configuration file. Each configuration item is reflected by a read/write attribute in the MBean. The list of attributes for the MBean is constructed at runtime by examining the file.
It is almost never necessary to implement the DynamicMBean
interface for an MBean whose management interface is known at compile time. If you need some particular abilities of Dynamic MBeans, for example the ability to supply descriptions for the attributes or operations, or the ability to disable certain attributes or operations, then you should consider subclassing javax.management.StandardMBean
rather than implementing the DynamicMBean
interface. That means that the management interface is still described by a Java interface, which in turn means that clients can use that interface to make a proxy for convenient access.
Model MBeans are a particularly advanced feature of the JMX API. It is perfectly possible to produce an entirely adequate set of MBeans without ever using Model MBeans. Model MBeans are harder to program so they should only be used when there is a clear benefit.
The two main features of Model MBeans that are potentially useful are:
MBeanInfo
and MBean*Info
(e.g. MBeanAttributeInfo
) classes, in the form of descriptors.The predefined Model MBean class RequiredModelMBean
additionally can support features such as logging and persistence, but, because these are optional, portable code can not rely on them.
Item 1, concerning indirection, is usually a minor convenience. It may be useful when creating MBeans remotely, however, since the RequiredModelMBean
class is guaranteed to be present, whereas a custom MBean class that exposes methods of another Java object might not be.
Item 2, concerning additional metadata, may be useful in certain applications.
The Jakarta Commons Modeler from the Apache Software Foundation builds on the Model MBean framework to allow you to specify MBean interfaces, including descriptors etc, using XML. The attributes and operations mentioned in XML are forwarded to an instance of a Java class that does not have to be an MBean class. The main advantage of this approach is perhaps that the entire information model can be contained in a single XML file. The main disadvantage is that there is no Java interface corresponding to the management interface of a given MBean, which means that clients cannot construct proxies.
An MBean is not just an interface. There must be some behavior associated with that interface. Most often, that behavior depends on the application or resource being managed or monitored. In other words, the MBean must interact with other Java objects.
The simplest way to achieve this is to take an existing Java object that is part of the application and make it into an MBean. For example, you might take a Cache object and add a CacheMBean interface to it. Then your object is both an integral part of the application and a remotely-accessible management point.
From an object-oriented programming standpoint, it is preferable not to make the same object do two different things. Here the Cache object is both a cache and the management of a cache, which violates this principle. An alternative approach is for the MBean to be a separate object that has a reference to the application object. Very often this reference is passed as a parameter to the constructor of the MBean. As mentioned above, Model MBeans provide one way to do this. Another way is to code a Standard MBean as illustrated here:
public class Cache { // the original application object
...
public int getSize() {...}
...
}
public class CacheManager implements CacheManagerMBean {
private final Cache cache;
public CacheManager(Cache cache) {
this.cache = cache;
}
public int getSize() {
return cache.getSize();
}
...
}
public interface CacheManagerMBean {
public int getSize();
...
}
The original Cache object from the application is unaware of management concerns. It only needs to export enough methods to allow the separate CacheManager MBean to manage it.
An MBean references data types in several places:
getAttribute
, setAttribute
, or invoke
can throw an exception. In Standard MBeans, these exceptions are thrown from the getter, setter, or operation method; if they are checked exceptions they must be declared in the throws
clause of the method. However, the set of thrown exceptions is not in general visible to management clients.javax.management.Notification
. In addition, its userData
field can refer to a Java object of any reference type.It is often necessary or desirable for these data types to be more complex than simple Java types such as int
or String
. In particular, when an attribute represents a snapshot of values such as the state of a thread at a given moment, it may not be meaningful to see a set of different values taken at different times. That could lead to meaningless results such as a thread that appears to be both running and waiting for a certain lock.
It is sometimes possible to address situations like this by breaking out the set of values into separate attributes and stipulating that those attributes must be read or written with a single getAttributes or setAttributes operation. But this tends to be a special-case solution that is difficult to implement and error-prone when clients fail to respect the requirement for using a single operation. It is also fragile in the face of evolution of the model. For example it does not extend to operation parameters or return values, and it does not work well if the values broken out into attributes are themselves complex types. So in general it is better to have a single data type representing the atomic set of values.
Complex data types can also be represented by defining model-specific types, for example a ThreadInfo
type for a snapshot of a thread. But this solution presents its own problems. Chief among these is that the client must have the same Java classes available. This is very inconvenient for model-neutral clients such as generic JMX consoles. It also potentially causes problems for access from languages other than Java (for instance, from scripts). And it can lead to serious class-loading headaches if the same client must interact with servers that have different versions of the model-specific classes. Finally, using arbitrary Java types can lead to unforeseen problems with serialization, since when an object is serialized other objects that it references may also be serialized, meaning that a client must have the classes for those other objects available too.
RMI can be configured to automatically download classes from server to client, or more rarely from client to server, that are not already present. Jini is fundamentally based on this idea, for example. However, this approach is generally not recommended for JMX-based management for a number of reasons:
The JMX API addresses the question of complex data types with Open MBeans. A normal MBean has an MBeanInfo that describes the names and types of its attributes, operations, and notifications. An Open MBean defines an OpenMBeanInfo that supplements this information with an OpenType for each attribute and operation type in addition to its Java type. Only a predefined set of Java classes can be described with Open Types. Thus, if an MBean is an Open MBean, then any client can interact with its attributes and operations, without needing model-specific classes. Additionally, the OpenMBeanInfo can describe allowed and default values for attributes, parameters, and return values.
The classes that can be described in an Open Type are these:
java.lang
: Integer
, Boolean
, Void
etc.String
, Date
, ObjectName
, BigDecimal
, BigInteger
.CompositeData
, described below.TabularData
, described below.Notice that primitive types such as int
are not in this list. In practice there is no difference to a client between an access to int
and an access to java.lang.Integer
, because everything goes through reflection; but there is a difference between int[]
and Integer[]
. We expect that the next version of the JMX spec will allow primitive types (and arrays thereof) in Open MBeans; see bug 5045358.
The Java type of an attribute, parameter, or return value in an Open MBean can be CompositeData
. In this case, the corresponding OpenType
in the OpenMBeanInfo
will be CompositeType
. The CompositeType
defines a set of items or fields. Each item has a name and an associated type, which is an Open Type. Thus, a complex thread state attribute can be represented in an Open MBean by an attribute whose Java type is CompositeData
and whose corresponding Open Type is a CompositeType
that describes the names and types of the thread state items.
The Java type of an attribute, parameter, or return value in an Open MBean can be TabularData
. In this case, the corresponding OpenType
in the OpenMBeanInfo
will be TabularType
. The TabularType
describes a table in which each row is a CompositeData
(with a single CompositeType
describing all rows), and one or more items in the CompositeData
are identified as forming the key for the table.
Coding Open MBeans leads to guaranteed interoperability with respect to data types.
Coding Open MBeans is currently rather awkward, because they must be Dynamic MBeans. Thus, an alternative is to write MBeans that use only the Open MBean data types ( CompositeData
etc), but are not actually Open MBeans (their MBeanInfo
is not an OpenMBeanInfo
). Such MBeans can be coded as Standard MBeans.
For example, instead of referring to ThreadInfo
in your MBean, you would refer to CompositeData
. The CompositeData
in question would have the same fields ( name, blockedCount, suspended) as the original ThreadInfo
. For convenience, you can add conversion methods to the ThreadInfo
class:
public CompositeData toCompositeData();
public static ThreadInfo from(CompositeData cd);
It is possible to automate the conversion from arbitrary Java classes (with certain constraints) to Open-MBean-compatible classes.
Java SE 6 will add user-defined MXBeans. This makes it possible to use any Java type that respects a certain number of constraints. It converts types into Open Types such as CompositeType
where necessary. MXBeans already exist in java.lang.management in version 5.0 (Tiger) of the Java platform, but only a fixed set is defined.
Notifications should be instances of javax.management.Notification
or one of the subclasses from the javax.management
namespace. Information that does not fit into one of those subclasses should be conveyed by attaching a CompositeData
to the notification using the setUserData
method.
There is unfortunately no OpenMBeanNotificationInfo
class today (see bug 5072197). If such a class existed, it could be used to indicate what the CompositeData
in the userData for the notification(s) emitted by an MBean would look like.
A client can send an arbitrary NotificationFilter
to a server in an addNotificationListener
operation. However, using an application-specific filter class is subject to the same sorts of problems as detailed for model-specific classes above. It is recommended that only the standard filters defined in javax.management.*
be used. ( NotificationFilterSupport
, AttributeChangeNotificationFilter
, MBeanServerNotificationFilter
.)
It is recommended that exceptions thrown by MBeans be drawn from the standard set defined in the java.*
and javax.*
packages on the Java SE platform. If an MBean throws a non-standard exception, a client that does not have that exception class will likely see another exception such as ClassNotFoundException
instead.
Occasionally, an information model may contain data types that are self-referencing, that is where the definition of the type includes, directly or indirectly, a reference to the type itself. For example:
public interface Graph {
public Node[] getNodes();
}
public interface
Node {
public
Node[] getNeighbours();
}
There is no way to represent such a type directly using Open Types. This is a consequence of the fact that the classes representing Open Types are immutable. The problem can be avoided by adding a level of indirection. In the example, every Node
could be given a name:
public interface Graph {
public Map<String,Node> getNodes();
}
public interface Node {
public String[] getNeighbours();
}
This can now be mapped to Open Types, using a TabularData
to represent the Map<String,Node>
.
MBeanServerInvocationHandler
). Otherwise, you can still add new attributes and operations by putting them in a subinterface and leaving the original interface intact:
public interface CacheControl2MBean extends CacheControlMBean {
void saveConfiguration() throws IOException;
}
Given that you have specified that users must not implement your interface, the changes that you can make to it are essentially:
String
to Object
or from HashMap
to Map
), or from a primitive type to a wider primitive type (for example from int
to long
)Object
to String
)throws
clause.You cannot do any of the following, in each case because it would break code that accesses the MBean through MBeanServer
operations such as getAttribute
and invoke
, and/or because it would break compilation or execution of code that accesses the MBean through a dynamic proxy using MBeanServerInvocationHandler
:
throws
clause (this includes replacing an exception by a subclass or superclass).If your MBean is a Dynamic MBean and does not have a Standard MBean interface that clients can use to make a dynamic proxy, then the above restrictions are slightly relaxed and the following become possible:
throws
clause in this case so the restriction on that is irrelevant.However it is strongly recommended to provide Standard MBean interfaces where possible because of the substantial increase in usability.
As described above, an MBean may use complex types in attributes, notifications, and operation parameters and return values. It is recommended that Open Types be used, which for complex types means ArrayType
, CompositeType
, or TabularType
.
Where one of these complex types appears in a model, it cannot in general be changed in a subsequent version. However, the contents of the type can be changed. So for example, in version 1 of a model, getThreadInfo
might return a CompositeData
with items name, blockedCount, suspended; while in version 2, getThreadInfo
still returns a CompositeData
, but it now also has a blockedTime item in addition to the previous ones. A version 1 client or server that gets the CompositeData
from a version 2 peer will simply ignore the new item. A version 2 client or server that gets the CompositeData
from a version 1 peer must be able to cope with the missing blockedTime item.
Arrays have no real structure, so there are no particular rules for their evolution. If the description of a model explicitly states constraints on the length of an array (for example, it is always 3, or it is always at most 8), then those constraints cannot usually be violated in a subsequent version without breaking existing code.
A TabularType
is characterized by its rowType
and its indexNames
. The indexNames
cannot change in a compatible evolution, because an older peer might try to call get(key)
with a key based on the former indexNames
. The rowType
, which is a CompositeType
, can change in accordance with the rules for CompositeType
detailed next.
A CompositeType
can evolve by adding new items. An older peer will ignore these items. This is not completely bullet-proof, because the older peer might call CompositeData.values()
and be surprised to see the extra values; or it might call oldCompositeType.isValue(newCompositeData)
which will currently return false
because of the extra items. ( CompositeData.values()
will probably be deprecated in a future version of the JMX API since it is of little use in practice.)
When a server adds new items, clients that know about the new items can refer to them, but they must also be prepared to handle the case when the new items are missing, because they are talking to an older server. Likewise when the server expects a CompositeData
to which new items have been added, it must be prepared to receive a CompositeData
from an older client, where the new items are missing.
Although the JMX API allows any object to be used as the source field of a notification, in practice it should be one of the following:
When the MBean Server is about to forward a notification whose source is a reference to the MBean sending the notification, it changes the source into the MBean's ObjectName. In this case, if the notification is forwarded to another MBean and then resent by that MBean, the MBean Server will not rewrite the source. So relying on this rewriting is discouraged.
Semantics of notification delivery
It is important to be aware of the semantics of notification delivery when defining how notifications are used in a model. Remote clients cannot assume that they will receive all notifications for which they are listening. The JMX Remote API only guarantees a weaker condition:
A client either receives all notifications for which it is listening, or can discover that notifications may have been lost.
A consequence of this weaker guarantee is that notifications should never be used to deliver information that is not also available in another way. The typical client observes the initial state of the information model, then reacts to changes in the model signalled by notifications. If it sees that notifications may have been lost, it goes back and observes the state of the model again using the same logic as it used initially. The information model must be designed so that this is always possible. Losing a notification must not mean losing information irretrievably.
When a notification signals an event that might require intervention from the client, the client should be able to retrieve the information needed to react. This might be an attribute in an MBean that contains the same information as was included in the notification. If the information is just that a certain event occurred, it is often enough just to have a counter of how many times it occurred. Then a client can detect that the event occurred just by seeing that the counter has changed.
A client can discover when notifications are lost by registering a listener using JMXConnector.addConnectionNotificationListener
.
The existing JMX protocols are not very well adapted to moving about large data values (on the order of tens of thousands of bytes or more). This is true for notification payloads as well as for attribute values being read or written and for parameters and return values of operations. Although large data values will work, there may be an effect on concurrent access using the same connector client. It is often preferable to send a URL that allows the other end (client or server) to access the large data value directly.
This is particularly important for notifications. Because of the way notification sending works in the current protocols, a notification may remain in the server for an indefinite period after being sent. Thus, if many large notifications are sent (for example because they have a very big string or byte array in the userData
field), an OutOfMemoryError
may result.
The article Apply JMX Best Practices from the Java Pro journal provides a set of higher-level recommendations that complement the ones in this document.