2

Our application exposes some methods via JMX, which we invoke using JVisualVM.

This works well normally, but sometimes a method call will abort with an exception inside the application. In that case, instead of showing the error message from the exception, JVisualVM shows an error message

java.rmi.UnmarshalException [...] error unmarshaling return; 
nested exception is: java.lang.ClassNotFoundException [...]

This is rather unhelpful and confusing; we would like JVisualVM to show the real error message.

What we have found so far:

It seems that JMX will serialize and deserialize any exception thrown during a call. In our case however, the exceptions are custom exceptions which are not part of the JDK. Thus when invoking the methods via JVisualVM, JVisualVM cannot show the exception because deserialization fails due to the unknown custom exception class.

Right now, as a workaround we wrap all methods exposed via JMX in a try-catch block which just does

    throw new RuntimeException("Error invoking method:"+e);

This works, because it converts any exception to a string, but seems rather unelegant and verbose.

  • Is there some generic way to tell JMX to not serialize exceptions? Something like "always convert exceptions to strings"?
  • We use Spring's MBeanExporter. Is there maybe a mechanism in Spring to handle this?

Edit

We know that we could configure JVisualVM to load the classes in question. However, we would like JVisualVM to work without a special config. Also, it may run an a system where the application code is not even available.

sleske
  • 81,358
  • 34
  • 189
  • 227
  • If you think that there is something we can change in VisualVM to help you with this issue, please let me know. – Tomas Hurka Apr 24 '12 at 19:13
  • @TomasHurka: Hey, it's good to get feedback directly from a developer :-). I'm afraid this is mostly a design problem with JMX. By the time VisualVM gets the unknown serialized exception, there's not much it can do. You could maybe make the error message a bit nicer. Instead of just dumping the exception hierarchy (UnmarshalException[...] nested: ClassNotFoundException etc.), you could show a message like "Error: The call returned an instance of class bla.myorg.com, which this Java VM does not know." Plus maybe a brief note on how to resolve this. – sleske Apr 25 '12 at 16:10
  • It would be great if you can provide some test app to simulate the problem. – Tomas Hurka May 09 '12 at 06:49
  • @TomasHurka: I'll try to whip up something. Might take a few days... – sleske May 09 '12 at 08:19
  • @TomasHurka: Sorry it took a while to respond. It's very easy to reproduce. Just whip up a small JMX program (I used the sample program from http://java.sun.com/developer/technicalArticles/J2SE/jmx.html ). Then modify `sayHello()` to throw a custom exception (just an empty static inner subclass of RuntimeException). If you invoke `sayHello()` via JMX, you get an ugly error message. – sleske Jun 29 '12 at 12:50
  • The message: "Problem invoking sayHello: java.rmi.UnmarshalException: Error unmarshaling return; nested exception is [...]". Should I file a feature request for this? Is http://java.net/jira/browse/VISUALVM the right place for filing? – sleske Jun 29 '12 at 12:53
  • Yes, http://java.net/jira/browse/VISUALVM the right place for filing this. – Tomas Hurka Jul 18 '12 at 13:55
  • I changed the error message and it now displays: "Problem invoking sayHello: java.lang.RuntimeException: Cannot instantiate remote class com.example.Hello$AAAAA". – Tomas Hurka Oct 02 '12 at 08:23
  • @TomasHurka: Yes, that sounds better. I'll try it out when it's released. Thanks! – sleske Oct 02 '12 at 08:27
  • Note that the title should be 'Avoid ClassNotFoundException in JMX ...' – Tomas Hurka Oct 02 '12 at 09:13

2 Answers2

1

Can you try playing with -Djava.rmi.server.codebase on JVisualVM so it can load the exception classes from the remote JVM?

Edit: If you don't want to do that, you could use an aspect to do the equivalent of your try-catch block. Then you only need it in one place. Spring AOP would make that quite easy if you're already using Spring.

artbristol
  • 32,010
  • 5
  • 70
  • 103
  • Yes, I could, but I am looking for a simple and robust solution. Also, JVisualVM may run an a system where the application code is not even available. – sleske Apr 20 '12 at 09:53
  • Anyway, good suggestions. I'd like something less complex than using aspects, bit it's still worth considering. – sleske Apr 25 '12 at 16:12
1

Take a look at java.io.Serializable. you can implement an optional method called writeReplace that can be placed in your custom exceptions. Return a generic exception with the original message. When the exception is serialized, it will send the generic. Or you could send a string too, but I'm not sure if that will work well with VisualVM. I guess it depends on how strict the data checking is.

Serializable classes that need to designate an alternative object to be used when writing an object to the stream should implement this special method with the exact signature:

ANY-ACCESS-MODIFIER Object writeReplace() throws ObjectStreamException;

===== Update =====

Got it. 3rd party libraries.... Summarizing.... The challenge here is that calling setAttribute[s] and invoke on MBeans through JVisualVM results in exceptions being thrown that are of types not available JVisualVM's classpath.

If you implemented some sort of interceptor on your MBeanServer, it could trap any thrown exceptions (perhaps any exception with a class name not starting with java. or javax. and re-throw them as RuntimeExceptions with a very rich error message representing the original exception. (If useful, you could even include a string rendering of the stack trace). This is basically what you're doing now except it has the virtue of being centralized in one place and has no reliance on how you implement your classes.

Although MBeanServer interceptors are still absent from the standard JMX specification, you can mimic them by creating your own MBeanServer implementation that simply delegates to the real MBeanServer, but for setAttribute[s] and invoke calls, you can override with the "catch-and-rethrow" technique. It might sound like major surgery, but sticky issues with complicated application-server JMX implementations aside, it's actually fairly straight forward.

See this stackoverflow post and this gist for an example.

.....having said that, you are using Spring and you can specify the exact MBeanServer instance where your MBeans are registered, as well as the MBeanServer that is exposed to the JMXConnectorServer being used by JVisualVM. So it would be simpler to:

  1. Use the MBeanServer wrapper idea outlined in the gist, construct it in Spring, injecting the real MBeanServer as the delegate
  2. Reference the wrapper bean in your Spring JMX exporter.
  3. Reference the wrapper bean in your JMXConnectorServer.

If your using the default JMXConnectorServer that bootstraps automatically, you would need to switch to using a Spring defined one.

The Spring based approach has the added benefit that only remote MBeanServer calls (like JVisualVM calls) will experience the exception rewriting, while internal calls in your application will get the real exception since they can go directly to the real MBeanServer.

===== One More Update =====

Spring JMX support does actually have an MBean interceptor implementation. I did not know about it, but as I was thinking about this issue, it sounded like something Spring would implement, and there it is.....

Community
  • 1
  • 1
Nicholas
  • 15,916
  • 4
  • 42
  • 66
  • Good idea, but the problematic exceptions mostly come from libraries I use, so I cannot change their classes. – sleske Apr 21 '12 at 12:52
  • Thanks for the suggestions. It seems awfully complex for such a simple problem :-(. Still, might be worth it if there are many exposed methods... – sleske Apr 25 '12 at 16:14
  • True.... I think it's one of those things that requires some effort to set it up the first time, but the next time you need another "interceptor" like function, it's super easy to add. – Nicholas Apr 25 '12 at 16:54