即使在vm args中使用XstartOnFirstThread也无效的线程访问

时间:2012-10-17 15:24:24

标签: java macos java-web-start

我有一个胚胎Java Web Start应用程序,只有一个类。它在Windows和Linux上运行,但在Mac OS X上遇到了可怕的无效线程访问错误。我意识到这已在其他地方处理过。我花了整整两天时间在互联网上搜索并实施了所有解决方案,但问题仍然存在。

我的理解是,必须从主线程调用SWT,这就是这里的情况。如果我错了,请纠正我。

我将在下面发布3个片段,应用程序的源代码,jnlp文件的相关部分以及Mac上的错误消息。问题在最后。


JAVA SOURCE CODE

package client;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Shell;
public class AccountWindow {
 public static void main(String[] args) {
  Display display = new Display(); **// error occurs here**
  Shell shell = new Shell(display); shell.open();
  while (!shell.isDisposed()) {
   if (!display.readAndDispatch())
    display.sleep();
  }
  display.dispose();
 }
}

JNLP SNIPPET

<resources os="Mac\ OS\ X" arch="x86_64">
    <j2se version="1.5+" java-vm-args="-XstartOnFirstThread" />
    <nativelib href="swt-4.2-cocoa-macosx-x86_64.jar" />
</resources>

错误消息

org.eclipse.swt.SWTException: Invalid thread access
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.SWT.error(Unknown Source)
    at org.eclipse.swt.widgets.Display.error(Unknown Source)
    at org.eclipse.swt.widgets.Display.createDisplay(Unknown Source)
    at org.eclipse.swt.widgets.Display.create(Unknown Source)
    at org.eclipse.swt.graphics.Device.<init>(Unknown Source)
    at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
    at org.eclipse.swt.widgets.Display.<init>(Unknown Source)
    at client.AccountWindow.main(AccountWindow.java:16)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.sun.javaws.Launcher.executeApplication(Launcher.java:1550)
    at com.sun.javaws.Launcher.executeMainClass(Launcher.java:1488)
    at com.sun.javaws.Launcher.doLaunchApp(Launcher.java:1299)
    at com.sun.javaws.Launcher.run(Launcher.java:114)
    at java.lang.Thread.run(Thread.java:637)

请注意
- 在http://www.eclipse.org/swt/faq.php#javawebstart发布的display.syncExec解决方案不适用,因为在您调用它之前需要显示。当我尝试创建显示时,会发生错误 - 我使用JaNeLa来验证jnlp文件并且没有红色错误 - &lt; resources os =“Mac \ OS \ X”arch =“i386”&gt;正在解释因为正在加载正确的swt库 - 您可以在http://thelinkjuicer.com/gannonline/client.jnlp

重现错误

现在问题 任何人都可以在源代码或jnlp片段中看到任何会导致错误的内容吗? 次要问题:如何判断VM是否实际读取了-XstartOnFirstThread参数?

2 个答案:

答案 0 :(得分:5)

显然,主要方法没有在主线程上执行。您可以在堆栈跟踪中看到启动器实际上是在另一个线程中启动的,然后Launcher仅间接调用main。不幸的是,这只是诊断,我不确定解决方案。我做了类似的事情(通过Java Web Start的SWT应用程序),但我不记得我们是如何解决这个问题的,如果有的话。

检查com.sun.javaws.Launcher源代码后,目前尚不清楚如何使其工作。 Launcher.launch方法启动一个新线程,在该线程中执行main方法。您可以按照代码重新创建您正在获得的精确堆栈跟踪。

Java Web Start的main entry point表明主线程在启动后很快就会死掉。

更新

我挖了一些东西:在this Eclipse bug report中,有人认为问题可能与此有关:

<resources>
  <j2se version="1.4+" />
  <jar href="client.jar" />
</resources>

解析器从这里获取j2se规范并忽略后面更具体的规范。尝试删除<j2se...行。

更新2

现在我从here

中挖出了这个
com.apple.concurrent.Dispatch.getInstance().getNonBlockingMainQueueExecutor().execute(
  new Runnable() { public void run() {
      final Display display = Display.getDefault(); 
      while (!display.isDisposed()) {
        if (!display.readAndDispatch())
          display.sleep();
      }
});

这实际上听起来像是可行的。它完全按照我在下面的评论中描述的那样:通过专门为此目的而实施的机制修补主线程。尝试根据您的需要进行调整。您可能甚至不需要-XstartOnFirstThread。

更新3

我终于找到了我的旧SWT-JWS项目。它有这个:

<resources os="Mac OS X" arch="x86_64">
  <j2se version="1.6+" java-vm-args="-XstartOnFirstThread"/>
  <jar href="swt-cocoa-macosx-x86-64-3.6.2.jar" />
</resources>

它有效。它没有默认的j2se元素,此元素在特定于OSX的条目中显示为

答案 1 :(得分:0)

这是对第二个问题的回答,&#34;如何判断VM是否实际读取了-XstartOnFirstThread参数?&#34; (或相关问题,&#34;如何检测-XstartOnFirstThread是否已传递给VM?&#34;)我查看了java.lang.management.RuntimeMXBean.getInputArguments(),但-XstartOnFirstThread 不是包含在返回的List中。经过一些研究,我能够找到一些东西,所以我希望这可以帮助那些在我身边的人。

根据this link,启动器设置了几个环境变量。其中包括:

JAVA_MAIN_CLASS_pid
JAVA_STARTED_ON_FIRST_THREAD_pid

使用System.getenv()获取Map个环境变量。在那里,您可以遍历entrySet(),直到找到Entry的{​​{1}},其返回值以getKey()开头。如果发现的"JAVA_MAIN_CLASS_" Entry包含主类的名称,则可以使用密钥的其余部分来确定 pid

获得 pid 后,在环境getValue()中查找字符串"JAVA_STARTED_ON_FIRST_THREAD_pid"。如果它存在并且值为Map,则该过程以"1"开始。否则,该过程在没有标志的情况下启动。

这可能无法在未签名的WebStart应用程序中工作,因为默认情况下禁止使用-XstartOnFirstThread方法。但是在签名的webstart应用程序或常规Java应用程序中,这确实有效。

希望有所帮助,