在通过Java Webstart运行时,在invokeLater中发生了NullPointerException。

18
从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的安全模型中是否有任何改变可能会导致问题?如果是这样,我会期望出现一些安全异常,但也可能是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。


---- Update2:
以下是guruman在下面提出的一个解决方法(非常感谢)。不幸的是,所有对SwingUtilities.invokeLater(..)的调用都必须被替换,并且程序开始依赖于Sun JRE的内部API。
我仍然在寻找更通用的方法,而不是特定于Sun JRE。我认为这是一个JRE的bug。也许可以通过某种方式进行修补:RMI线程中的AppContext不应该为空。
---- 更新3:
我已经制作了一个简单的测试用例来展示这个问题。它包含4个文件。要运行这个测试用例,需要对目标jar文件(TestCase.jar)进行签名。首先,在launch.jnlp中指定正确的codebase,然后通过Java Web Start运行服务器(例如,使用javaws launch.jnlp)。屏幕上应该出现以下窗口:

The server application frame after start

然后可以执行RMI客户端。成功执行后,窗口应包含以下内容:

The server application frame after successful RMI call

但是如果你尝试使用JWS来执行服务器,你将在客户端程序中得到以下异常(该异常从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)

这里是测试用例文件。
JNLP文件定义launch.jnlp:
TestCase digital_infinity
TestCase TestCase
RMI接口定义(RmiService.java): package testcase; public interface RmiService extends java.rmi.Remote { void callBack() throws java.rmi.RemoteException; }
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); } 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); } }); } public void callBack() { Runnable rn = new Runnable() { public void run() { textField.setText("CallBack successfully called."); frame.pack(); } }; SwingUtilities.invokeLater(rn); } }
简单客户端代码: package testcase;
import java.rmi.registry.LocateRegistry; import java.rmi.registry.Registry; public class RmiClient { public static void main(String[] args) throws Exception { Registry reg = LocateRegistry.getRegistry(RmiServiceImpl.PORT); RmiService serv = (RmiService) reg.lookup("test"); serv.callBack(); } }

---- 更新4:

我提交的JRE Bug:https://bugs.java.com/bugdatabase/view_bug?bug_id=8019272

其他相关的Bug:


1
为什么要创建第二个事件队列? - trashgod
1
@trashgod 我从未使用过JWS,也许这个帖子可能会有趣 - mKorbel
1
@digital_infinity 因为异常涉及到 AWT-EventQueue-2,请确保我们没有使用 SecondaryLoop。 - mKorbel
2
@mKorbel:仔细检查后,我也发现了通过JWS运行时的额外队列,但应用程序正常运行。digital:另请参阅Initial Threads - trashgod
代码一直存在NullPointerException漏洞,但在以前的JVM版本中从未发生过这种情况,这是否可能?也许这些线程之间存在竞争条件。这样的问题可能不会在运行时始终引发异常,但随着外部环境的变化,这些问题可能开始出现,即使抛出异常的代码已经很长时间没有更改了。 - Byron Hawkins
显示剩余10条评论
5个回答

10

我发现了一个我认为是更好的解决此错误的方法。

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

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

由于我的应用程序需要,我能够将它添加到一个使用SwingUtilities的单个方法中,但您可能需要将其添加到RMI Callable Object的每个方法中。

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


我在一个JavaFX应用程序中使用WebStart时,使用java.imageio.ImageIO加载一些图像时遇到了同样的问题。我正在重用来自Swing库的图像处理代码,并将最终结果转换为FX兼容图像。在这里创建AppContext代码有助于解决这个问题。 - Martin Woolstenhulme

5
问题发生在Webstart环境中。在Java 7u25的Webstart版本之前,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);

1
有趣的见解 - 但真是个糟糕的事情:它会在安全受限环境下破坏现有的代码。 - kleopatra
我不创建 RMI 线程。它们在导出对象后由 RMI 环境创建。有没有办法更改 RMI 线程的创建?如果没有,我需要为应用程序中所有来自 RMI 的 invokeLater() 创建单独的机制。我的意思是创建一个带有一些输入队列的线程,该队列调用在队列中放置的对象的 invokeLater()。 - digital_infinity
2
如果您无法更改线程组的线程,则可以调用sun.awt.SunToolkit#invokeLaterOnAppContext(AppContext, Runnable)而不是SwingUtilities#invokeLater(Runnable)。 - guruman
您可以通过调用sun.awt.AppContext#getContexts()来获取所有的AppContext。它会返回一个Set<AppContext>,其中包含所有现有的AppContext。 - guruman
我也遇到了同样的问题,但是与SwingUtilities.isEventDispatchThread()有关 - 你有什么想法吗? (@digital_infinity) - kleopatra
显示剩余2条评论

3

这里提供了一个针对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<ThreadGroup, AppContext> threadGroup2appContext = (Map<ThreadGroup, AppContext>)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("<html>" + 
                "Our application cannot run using <b>Web Start</b> with this version of Java.<p><p>" +
                "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;
    }
}

2

Java 7u65于昨天发布(2014年7月15日),声称已经修复了这个或非常相似的问题,即JDK-8019724。我现在正在进行测试以找出 - 我们其中一位供应商的驱动程序在Java Web Start下无法运行,并且一直让我们使用Java 6。

预计完成时间:是的,看起来这解决了我们的问题!


0

这个问题在Mac Sierra上仍然存在。但是我可以通过调用以下方法来解决它:

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

就在我的第一个 SwingUtilities.invokeLater(...) 调用之前。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接