Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I safely solve this Java context classloader problem?

One and only one of my hundreds of users has trouble starting my Java desktop app. It only starts for him about one-third of the time. The other two-thirds of the time a NullPointerException is thrown at startup:

Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
    at java.util.Hashtable.put(Hashtable.java:394)
    at javax.swing.JEditorPane.registerEditorKitForContentType(JEditorPane.java:1327)
    at javax.swing.JEditorPane.registerEditorKitForContentType(JEditorPane.java:1309)
    at javax.swing.JEditorPane.loadDefaultKitsIfNecessary(JEditorPane.java:1387)
    at javax.swing.JEditorPane.getKitTypeRegistry(JEditorPane.java:1344)
    at javax.swing.JEditorPane.getEditorKitClassNameForContentType(JEditorPane.java:1340)
    at javax.swing.JTextPane.<init>(JTextPane.java:76)
    at myapp.Launcher$1.run(Launcher.java:13)
    at java.awt.event.InvocationEvent.dispatch(InvocationEvent.java:209)
    at java.awt.EventQueue.dispatchEvent(EventQueue.java:633)
    at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:296)
    at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:211)
    at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:201)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:196)
    at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:188)
    at java.awt.EventDispatchThread.run(EventDispatchThread.java:122)

I've followed the stack trace to find that the cause is that

Thread.currentThread().getContextClassLoader()

in JEditorPane is returning null.

Googling reveals that this is a sporadic, very infrequent, and mysterious problem that affects a few people.

My question is, what can I do as a work-around? This might work, if I call it before creating an EditorPane:

Thread.currentThread().setContextClassLoader(MyClass.class.getClassLoader());

But I don't really understand classloaders as well as I would like (and I've tried to understand them better). I feel that changing the contextClassLoader in the EDT could have bad ramifications.

Any ideas what I can do?

EDIT: I had some correspondence with someone who understands Java ClassLoaders well. It seems this is an obscure ClassLoader race condition. That is, a bug in Java.

like image 668
Steve McLeod Avatar asked Dec 06 '09 16:12

Steve McLeod


1 Answers

Thread.currentThread().getContextClassLoader()

If the code in JEditorPane.registerEditorKitForContentType does not check for a null return value in the above code, this is a bug in JEditorPane. Note that MyClass.class.getClassLoader() may also return null. The only one you can rely on is the system ClassLoader.

The pattern for setting the context ClassLoader for an invocation usually looks something like this:

Thread thread = Thread.currentThread();
ClassLoader old = thread.getContextClassLoader();
thread.setContextClassLoader(fooClassLoader);
try {
  // do call that depends on context ClassLoader
} finally {
  thread.setContextClassLoader(old);
}

The value that should be set via setContextClassLoader will depend on the intent of the code that is consuming it and the design of the ClassLoader framework you are running in.

In a stand-alone application, you can probably get away with just using this ClassLoader (passing in a ref to the current class):

private ClassLoader findClassLoaderForContext(Class<?> c) {
  ClassLoader context = Thread.currentThread().getContextClassLoader();
  ClassLoader me = c.getClassLoader();
  ClassLoader system = ClassLoader.getSystemClassLoader();
  return (context == null) ? (me == null) ? system : me : context;
}

In a ClassLoader-sensitive plug-in framework (a Java EE server would be a prime example), it would pay to understand the nature and usage of the loading scheme.

like image 82
McDowell Avatar answered Oct 18 '22 19:10

McDowell