在通过Java Webstart运行时,invokeLater中出现NullPointerException

从JRE 1.7.0_21升级到1.7.0_25-b15后,我的应用程序在从Java WebStart运行时开始在SwingUtilities.invokeLater(…)中抛出NullPointerException。 令人惊讶的是,它作为一个独立的应用程序(在JWS之外)执行时,效果很好。

这是堆栈的顶部:

Exception in thread "AWT-EventQueue-2" java.lang.NullPointerException at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011) at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007) at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002) at java.awt.Toolkit.getEventQueue(Toolkit.java:1730) at java.awt.EventQueue.invokeLater(EventQueue.java:1217) at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290) at AppletView$8.setBaseUnits(AppletView.java:536) (...) 

为了全面了解:方法setBaseUnits(..)被远程服务器从RMI调用作为回调。 完整的堆栈跟踪很长。

安全模型中是否有某些内容在RMI或JWS中发生了变化,可能会破坏事物? 如果是这样,我会期待一些安全性exception,但它可能是在JRE中未正确检测到的并导致NPE。

任何建议表示赞赏。


—-更新1

JRE 1.7.0_25更新可能存在类似的问题,可能涉及一些安全更改和AppContext对象: https : //forums.oracle.com/message/11080621 https://forums.oracle.com/thread/2552799 。 我试过建议修复: https : //forums.oracle.com/message/11082162#11082162但没有任何成功。

我可以在我的应用程序中看到3个AWT-EventQueue线程,数字从0到2.看起来如果JWS启动程序,JRE会为不同的应用程序上下文创建额外的事件队列。 JWS中有3个AppContext和3个EVT,如果从IDE执行程序,则只有一个上下文和EVT。


—-更新2

下面的guruman建议有一个解决方法(非常感谢)。 不幸的是,必须替换来自RMI线程的所有对SwingUtilities.invokeLater(..)的调用,并且程序开始依赖于Sun JRE内部API。

我仍在寻找更具体的方法,不是针对Sun JRE的。 我认为这是一个JRE错误。 也许它可以以某种方式修补:在RMI线程中AppContext不应为null。


—-更新3:

我做了一个简单的测试用例来说明问题。 它包含4个文件。 要运行此测试用例,需要签署目标jar(TestCase.jar)。 首先在launch.jnlp中指定正确的代码库 ,然后通过Java Web Start运行服务器(例如,使用javaws launch.jnlp)。 屏幕上应显示以下框架:

启动后的服务器应用程序框架

然后可以执行RMI客户端。 成功执行后,框架应包括:

成功进行RMI调用后的服务器应用程序框架

但是如果您尝试使用JWS执行服务器您将在客户端程序中获得以下exception(exception从RMI服务器传播到RMI客户端):

 Exception in thread "main" java.lang.NullPointerException at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1011) at sun.awt.SunToolkit.getSystemEventQueueImplPP(SunToolkit.java:1007) at sun.awt.SunToolkit.getSystemEventQueueImpl(SunToolkit.java:1002) at java.awt.Toolkit.getEventQueue(Toolkit.java:1730) at java.awt.EventQueue.invokeLater(EventQueue.java:1217) at javax.swing.SwingUtilities.invokeLater(SwingUtilities.java:1290) at testcase.RmiServiceImpl.callBack(RmiServiceImpl.java:70) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at sun.rmi.server.UnicastServerRef.dispatch(UnicastServerRef.java:322) at sun.rmi.transport.Transport$1.run(Transport.java:177) at sun.rmi.transport.Transport$1.run(Transport.java:174) at java.security.AccessController.doPrivileged(Native Method) at sun.rmi.transport.Transport.serviceCall(Transport.java:173) at sun.rmi.transport.tcp.TCPTransport.handleMessages(TCPTransport.java:553) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run0(TCPTransport.java:808) at sun.rmi.transport.tcp.TCPTransport$ConnectionHandler.run(TCPTransport.java:667) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:615) at java.lang.Thread.run(Thread.java:724) at sun.rmi.transport.StreamRemoteCall.exceptionReceivedFromServer(StreamRemoteCall.java:273) at sun.rmi.transport.StreamRemoteCall.executeCall(StreamRemoteCall.java:251) at sun.rmi.server.UnicastRef.invoke(UnicastRef.java:160) at java.rmi.server.RemoteObjectInvocationHandler.invokeRemoteMethod(RemoteObjectInvocationHandler.java:194) at java.rmi.server.RemoteObjectInvocationHandler.invoke(RemoteObjectInvocationHandler.java:148) at com.sun.proxy.$Proxy0.callBack(Unknown Source) at testcase.RmiClient.main(RmiClient.java:22) 

所以这里是测试用例文件:

1)JNLP文件定义launch.jnlp:

    TestCase digital_infinity  TestCase TestCase             

2)RMI接口定义(RmiService.java):

 package testcase; public interface RmiService extends java.rmi.Remote { void callBack() throws java.rmi.RemoteException; } 

3)RMI服务代码和服务主类:

 package testcase; import java.rmi.RemoteException; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; import javax.swing.JFrame; import javax.swing.JTextField; import javax.swing.SwingUtilities; /** */ public class RmiServiceImpl extends java.rmi.server.UnicastRemoteObject implements RmiService { final static int PORT = 1099; static JFrame frame; static JTextField textField; public RmiServiceImpl() throws RemoteException { super(PORT); } /** * @param args the command line arguments */ public static void main(String[] args) throws Exception { Registry reg; RmiServiceImpl service = new RmiServiceImpl(); try { reg = LocateRegistry.getRegistry(PORT); reg.rebind("test", service); } catch (RemoteException ex) { reg = LocateRegistry.createRegistry(PORT); reg.rebind("test", service); } SwingUtilities.invokeAndWait(new Runnable() { @Override public void run() { frame = new JFrame("Test App"); textField = new JTextField("Before call to callBack"); frame.getContentPane().add(textField); frame.pack(); frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); frame.setVisible(true); } }); } /** RMI callback */ public void callBack() { Runnable rn = new Runnable() { public void run() { textField.setText("CallBack succesfully called."); frame.pack(); } }; SwingUtilities.invokeLater(rn); } } 

4)简单的客户端代码:

 package testcase; import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RmiClient { public static void main(String[] args) throws Exception { //now we trying to communicate with object through RMI Registry reg = LocateRegistry.getRegistry(RmiServiceImpl.PORT); //after got the registry, lookup the object and finally do call RmiService serv = (RmiService) reg.lookup("test"); serv.callBack(); } } 

—-更新4:

我提交的JRE Bug: http : //bugs.sun.com/bugdatabase/view_bug.do?video_id = 8019272

其他相关错误:

  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8019274
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8028290
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8017770
  • http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=8021370

Webstart环境中出现此问题。 在Webstart版本的Java 7u25之前,在系统线程组上设置了AppContext。 然而它是在主线程组上设置的。

如果您有一个基于线程组的线程,其父或祖父不是主线程组,则它没有sun.awt.AppContext。

您应该根据安全管理器的线程组(如果存在)创建线程。

 Runnable task = .... ThreadGroup threadGroup = System.getSecurityManager() != null ? System.getSecurityManager().getThreadGroup() : Thread.currentThread().getThreadGroup(); Thread t = new Thread(threadGroup, task, "my thread", 0); 

我找到了我认为更好的解决这个bug的方法。

我在调用SwingUtilities或任何与Swing相关的组件方法之前添加了以下代码。 它为RMI线程创建一个新的AppContext(当运行下面的代码时,RMI线程必须是当前的线程)。

 if(AppContext.getAppContext() == null){ SunToolkit.createNewAppContext(); } 

由于我的应用程序的需要,我能够在使用SwingUtilities的单个方法上添加它,但您可能需要将它添加到RMI可调用对象上的每个方法。

代码只需运行一次,因此请检查应用程序的行为。

以下是JDK-8019274的解决方法,打包在实用程序类中。 对我们来说,invokeAndWait()仍然是一个问题。 此示例具有invokeLater()的现有修复程序和invokeAndWait()的新修复程序。

笔记:

  • 您需要在项目中包含jnlp.jar。
  • 在调用invokeLater()之前,在main()方法的早期调用init()
  • 用这些调用替换对SwingUtilities invokeLater()和invokeAndWait()的所有调用

(免责声明:这是我们的产品。此解决方案的某些方面可能不适用于您。)

 public class JreFix { private static String badVersionInfo = null; private static AppContext awtEventDispatchContext = null; private static AppContext mainThreadContext = null; private static Boolean isWebStart = null; private static BasicService basicService = null; private static IntegrationService integrationService = null; /** * Call this early in main(). */ public static void init() { if (isWebstart() && isApplicableJvmType()) { String javaVersion = System.getProperty("java.version"); if ("1.7.0_25".equals(javaVersion)) { badVersionInfo = "7u25"; } else if ("1.7.0_40".equals(javaVersion)) { badVersionInfo = "7u40"; } else if (javaVersion != null && "1.6.0_51".equals(javaVersion.substring(0,8))) { badVersionInfo = "6u51"; } else if ("javaws-10.25.2.16".equals(System.getProperty("javawebstart.version"))) { badVersionInfo = "Web Start 10.25.2.16"; } } if (badVersionInfo != null) { mainThreadContext = AppContext.getAppContext(); try { SwingUtilities.invokeAndWait(new Runnable() { public void run() { awtEventDispatchContext = AppContext.getAppContext(); } }); } catch (Exception e) { displayErrorAndExit(null); } if (mainThreadContext == null || awtEventDispatchContext == null) { displayErrorAndExit(null); } } } public static void invokeNowOrLater(Runnable runnable) { if (hasAppContextBug()) { invokeLaterOnAwtEventDispatchThreadContext(runnable); } else { SwingUtilities.invokeLater(runnable); } } public static void invokeNowOrWait(Runnable runnable) { if (hasAppContextBug()) { fixThreadAppContext(null); } try { SwingUtilities.invokeAndWait(runnable); } catch (Exception e) { // handle it } } public static boolean hasAppContextBug() { return isJreWithAppContextBug() && AppContext.getAppContext() == null; } public static void invokeLaterOnAwtEventDispatchThreadContext(Runnable runnable) { sun.awt.SunToolkit.invokeLaterOnAppContext(awtEventDispatchContext, runnable); } public static void fixThreadAppContext(Component parent) { try { final Field field = AppContext.class.getDeclaredField("threadGroup2appContext"); field.setAccessible(true); Map threadGroup2appContext = (Map)field.get(null); final ThreadGroup currentThreadGroup = Thread.currentThread().getThreadGroup(); threadGroup2appContext.put(currentThreadGroup, mainThreadContext); } catch (Exception e) { displayErrorAndExit(parent); } if (AppContext.getAppContext() == null) { displayErrorAndExit(parent); } } private static boolean isJreWithAppContextBug() { return badVersionInfo != null; } private static void displayErrorAndExit(Component parent) { JLabel msgLabel = new JLabel("" + "Our application cannot run using Web Start with this version of Java.

" + "Java " + badVersionInfo + " contains a bug acknowledged by Oracle (JDK-8019274)."); JOptionPane.showMessageDialog(parent, msgLabel, "Java Version Error", JOptionPane.ERROR_MESSAGE); System.exit(1); } private static boolean isApplicableJvmType() { String vendor = System.getProperty("java.vendor"); String vmName = System.getProperty("java.vm.name"); if (vendor != null && vmName != null) { return vmName.contains("Java HotSpot") && (vendor.equals("Oracle Corporation") || vendor.equals("Sun Microsystems Inc.")); } return false; } private static boolean isWebstart() { if (isWebStart == null) { try { basicService = (BasicService) ServiceManager.lookup("javax.jnlp.BasicService"); isWebStart = true; } catch (UnavailableServiceException e) { isWebStart = false; } try { integrationService = (IntegrationService) ServiceManager.lookup("javax.jnlp.IntegrationService"); } catch (UnavailableServiceException e) { } } return isWebStart; } }

昨天发布的Java 7u65(2014-07-15)声称在JDK-8019724中解决了这个或非常类似的问题。 我现在正在测试 – 我们的某个供应商的驱动程序在Java Web Start下无法正常运行,它一直在使用Java 6。

ETA:是的,看起来这样可以解决我们的问题!

这仍然发生在Mac Sierra上。 但是我可以通过调用来解决它:

  if (sun.awt.AppContext.getAppContext() == null) { sun.awt.SunToolkit.createNewAppContext(); } 

就在我第一次调用SwingUtilities.invokeLater(…)之前。