In the last article, we showed how you can use JConsole to manage a local application or just peer into the JVM for a view of how it is working. Useful as this is in development, out in the field, the chances are the application you want to manage is going to be at the far end of a network.
The JMX, Java Management Extensions, originally appeared with no standard network support, gaining that in a later revision, JSR 160. That added with a whole raft of networking support to JMX, so much that it can be a bit overwhelming browsing the specification.
Let's start with using JConsole remotely without worrying about security first.
The important element in getting a quick remote connection is enabling the application to be monitored to accept remote connections. This can be done by setting some system properties, in the same way you turn JMX on.
To start the example application WatchMe with remote access, you'd start it with:
This starts JMX and opens port 9696--you are able to use any free port--remember to make sure it isn't blocked by a local firewall though. The second pair of parameters disables the password and SSL authentication. Never, ever, use this in a production environment--it basically opens the door to all comers to manage your application, though this is useful in the development lab. To connect to the application, start up JConsole as we previously covered, but instead of going to the Local tab in the connections dialogue, go to the Remote tab.
Enter the hostname and port of the target application, leave username and password blank and click connect and that's it. You can now monitor and manage the application with JConsole.
JMX gives two ways to control who logs into your applications management interface, Password and SSL Authentication. Password authentication is the simplest part to set up. In your JRE's home directory you'll find the directory lib, and below that a management directory, which in turn contains JMX's control files. There isn't a password file by default, but there is a template file, jmxremote.password.template. If you copied this file to management.jmxremote.password it would be used as that JRE's default password file.
The password file template is made up of pairs of role names and passwords. Here's the default template (and yes these are commented out; you wouldn't want a template with a password already set up in it).
# monitorRole QED
# controlRole R&D
From which of course, you'd remove the comments and change the passwords. They are, as you can see, plaintext passwords. The names are derived from the roles defined in jmxremote.access file which by default looks like this:
Where the name is followed by what access type the role is granted. There are only two, readonly for users who can read the management information but aren't allowed to update it or invoke methods, and readwrite which gives full access to the management beans.
If you want an application to use a particular password file, you can copy the password template file your home directory and edit it. To run an application with password authentication, first we need to note that the password authentication property is true by default, which is why we had to force it to false in previous examples. Now we can just omit setting that property. What we do need to do is point the application at the password file; for this example, we'll assume we're on Windows and have created a password file as C:\passwords\jmxremote.password containing:
To run with this password file, you'd execute
and immediately wonder why you got an error about the password file not being restricted. The reason is simple; the passwords are plain text so the JMX authentication demands that the password file is only readable/writable by the same owner as is running the application.
Unix users will immediately reach for the chmod command and execute "chmod 600 passwordfilename", but Windows users have a more fiddly time. Firstly, the Windows file system has to be capable of setting that kind of ownership. If you have a FAT32 file system, you are out of luck; FAT32 doesn't have the concept of ownership.
If you have NTFS, then you can establish ownership. If you go to the Properties of the password file, and select the Security tab, take ownership of the file, remove "inherit from parent" permissions and then remove all but your own permissions, then you will be able to run the application. If you can't see the Security tab, you need to turn off "Simple File Sharing" on the folder. There's a screen by screen version of this process at the Java Web site.
Once the application is running, you can connect to it using the Remote tab on JConsole and enter the appropriate username and password. You now have basic authentication.
For production level security, JMX supports SSL authentication. This requires an exchange of keys between the client and server to allow them to authenticate each other and can be used in combination with the password authentication. The bad news is that JConsole itself doesn't let you set up the keystore, so no SSL support.
Notification is one element of what an MBean can do that we haven't touched on. When a state change or some other significant event occurs within an object, you may want to be able to signal to management clients that something has happened. The JMX Notification framework is quite rich, but we'll dive in and extend the WatchMe example code to generate a simple logging notification whenever the counter is divisible by 10.
For an MBean to generate a notification, it needs to implement the NotificationEmitter interface (which supercedes an earlier NotificationBroadcaster interface).
public class WatchMeBean implements Runnable,WatchMeBeanMBean,NotificationEmitter
Rather than implement the required methods ourselves we can use the NotificationBroadcasterSupport class (which actually implements NotificationEmitter now) to manage our notifications and delegate the work to that.
private NotificationBroadcasterSupport nbs=new NotificationBroadcasterSupport();
NotificationBroadcasterSupport actually implements the NotificationEmitter interface, so we have four methods to delegate.
And now we have a MBean capable of emitting Notifications. You'll notice the same pattern as used for handling PropertyChange event generation in the WatchMe bean. One thing with Notifications, unlike PropertyChange events, is that they require a sequence number for each notification.
And finally we can get to generating a notification. First create a Notification object; we use the simplest constructor, which takes a string as its payload.
Notification notif=new Notification("uk.builder.watchme.log",this,_seq++,"Count is divisible by 10");
The first parameter is a string denoting a 'class' of notification. The next is a source for the notification, and then comes the sequence number, which we increment. Finally, we have the message we want to send. We then hand this Notification instance to the NotificationBroadcastSupport instance for dispatch.
And that's almost it. We want to see these Notifications too. Run the application (using the replacement example code for WatchMeBean.java in Listing A) and connect to the application using JConsole.
Under the MBeans tab, select the WatchMe MBean, and then select the Notification tab. You don't see Notifications unless you subscribe to them, so click on the Subscribe button and wait… you should see your first notifications.
Logging and JConsole
Of course, you may already use java.util.logging for your logging needs, and if you do, you will be pleased to find that it is exposed as a standard MBean in Java 1.5. You can use JConsole to raise or lower logging levels on any instance of a Logger. In the example code, I've added some logging messages. Run the example and connect JConsole to it. Select the MBeans tab, open java.util.logging in the MBeans tree and select Logging. You'll see the Attributes tab has one attribute, LoggerNames. Click on the attributes Value field and you will see all the names of the logs configured.
You should be able to find "watchme" in this list. Click on the Operations tab and you'll see the three methods you can invoke, getParentLoggerName, getLoggerLevel and setLoggerLevel.
On setLoggerLevel, p0 is the name of the Logger and p1 is a string representation of the desired level. The level is an upper case string or integer value, as handled by the Level.parse method; Enter 'watchme' for p0, and 'OFF' for p1 and click the setLoggerLevel button and logging is turned off (keep an eye on the console). Enter 'ALL' in p1 for all logging messages.
When I was putting this example together, I noticed initially that I couldn't apparently set the level for more detail than the "INFO" level. That's because the Logging MBean only sets the Logger instance's level, not any Handlers which may be associated with the log. That's why in the WatchMeBean constructor there's this code:
if(h instanceof ConsoleHandler) h.setLevel(Level.ALL);
Which sets any associated ConsoleHandlers to show all logging messages. Without this, you wouldn't see the FINE, FINER, FINEST level messages on the console.
JConsole isn't a feature rich JMX client, but it is a standard part of the dev kit in J2SE5.0 which means it should always be "within reach" if you need to locally or remotely manage an application or monitor a JVM. JConsole is not the only client by any means; the MX4J is an open source JMX implementation with its own console, and you can of course implement your own JMX clients to orchestrate your application management (for example, monitoring throughput in a cluster of backend servers). JConsole is your entry point into taking remote control of your applications.