【问题标题】:OSX: JavaVM, AWT/Swing and possibly a deadlockOSX:JavaVM、AWT/Swing 和可能的死锁
【发布时间】:2012-02-03 18:15:27
【问题描述】:

我对 Java 编程真的很陌生,因此如果这听起来像一个愚蠢的问题,我提前道歉。

我正在尝试构建一个用纯 C 编写的简单应用程序,它必须创建一个 JavaVM,然后通过加载基于 AWT/Swing 的 java 代码来创建一个新窗口。

根据this 技术说明,我了解到,仅在 Mac OSX 中,JavaVM 必须从不同于主线程的线程调用,以便能够创建基于 AWT 的 GUI。

因此,在我的 C 应用程序的 main 函数中,我创建了一个新线程来执行所有操作,从创建 javaVM 到创建 GUI。

由于应用程序实际上并不那么简单,我将发布一个简化版本。

主要功能:

int main(int argc, char** argv)
{

    // Run-time loading of JavaVM framework

    void *result;

    result = dlopen("/System/Library/Frameworks/JavaVM.framework/JavaVM", RTLD_LAZY);
    if (!result) {
        printf("can't open library JavaVM: %s\n", dlerror());
    }
    else {
        printf("library JavaVM loaded\n");
    }

    /* Start the thread that runs the VM. */
    pthread_t vmthread;

    // create a new pthread copying the stack size of the primordial pthread
    struct rlimit limit;
    size_t stack_size = 0;
    int rc = getrlimit(RLIMIT_STACK, &limit);
    if (rc == 0) {
        if (limit.rlim_cur != 0LL) {
            stack_size = (size_t)limit.rlim_cur;
        }
    }


    pthread_attr_t thread_attr;
    pthread_attr_init(&thread_attr);
    pthread_attr_setscope(&thread_attr, PTHREAD_SCOPE_SYSTEM);
    pthread_attr_setdetachstate(&thread_attr, PTHREAD_CREATE_DETACHED);
    if (stack_size > 0) {
        pthread_attr_setstacksize(&thread_attr, stack_size);
    }


    /* Start the thread that we will start the JVM on. */
    pthread_create(&vmthread, &thread_attr, startJava, (void *)&thread_data_struct);
    pthread_attr_destroy(&thread_attr);

    pthread_exit(NULL);

    return 0;
}

线程函数:

void *startJava(void *jvm_lib)
{

    JavaVMInitArgs args;

    const char* classpath = getenv("CLASSPATH");

    // determine classpath
    char* classpath_opt = str_printf("-Djava.class.path=%s", classpath);

    JavaVMOption* option = malloc(sizeof(JavaVMOption) * 2);
    option[0].optionString = classpath_opt;
    option[1].optionString = str_printf("-verbose:jni");    

    args.version = JNI_VERSION_1_6;
    args.nOptions = 2;
    args.options = option;
    args.ignoreUnrecognized = JNI_FALSE; // don't ignore unrecognized options

    fptr_JNI_CreateJavaVM JNI_CreateJavaVM_fp = (fptr_JNI_CreateJavaVM)dl_dlsym(jvm_lib,
            "JNI_CreateJavaVM");

    int result = JNI_CreateJavaVM_fp(&jvm, (void**) &env, &args);
    free(option);
    free(classpath_opt);

    // launch java code
    jclass init_class = (*env)->FindClass(env, "org/classes/Loader");

    jmethodID load_id = (*env)->GetStaticMethodID(env, init_class, "Load",
        "(Ljava/lang/String;Lorg/classes/stuff;J)V");

    (*env)->CallStaticVoidMethod(env, init_class, load_id);
}

Java 代码:(已更新)

package org.classes;

import java.awt.AWTException;
import java.awt.Component;
import java.awt.Frame;
import java.awt.image.BufferedImage;
import java.awt.EventQueue;

public class Loader {
    public static void Load(String baseDir, Stuff stuff, long nativePointer)
    {
      EventQueue.invokeLater(new Runnable() {
      public void run() {
              System.loadLibrary("drawingHelperLibrary");

              ...
              ...
              ...

              // start test window
              Frame frame = new Frame();
              frame.setSize(640,480);
              frame.setLocation(50, 50);
              frame.setVisible(true);

              }
       });
     }
}

上述所有代码都成功执行,除了导致死锁或类似情况的窗口的创建,因为终端保持忙碌而没有任何 CPU 使用,并且两个线程都保持活动状态。

如果我注释掉有关创建窗口的行,应用程序将成功执行并退出。

这是 jstack 的输出:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (20.4-b02-402 mixed mode):

"Attach Listener" daemon prio=9 tid=1040b1800 nid=0x11b888000 waiting on condition [00000000]
   java.lang.Thread.State: RUNNABLE

"Low Memory Detector" daemon prio=5 tid=103806000 nid=0x10b137000 runnable [00000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" daemon prio=9 tid=103805800 nid=0x10b034000 waiting on condition [00000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" daemon prio=9 tid=103804800 nid=0x10af31000 waiting on condition [00000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" daemon prio=9 tid=103804000 nid=0x10ae2e000 runnable [00000000]
   java.lang.Thread.State: RUNNABLE

"Surrogate Locker Thread (Concurrent GC)" daemon prio=5 tid=103803000 nid=0x10ad2b000 waiting on condition [00000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" daemon prio=8 tid=10409b800 nid=0x10ac28000 in Object.wait() [10ac27000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <7f3001300> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:118)
    - locked <7f3001300> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:134)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:159)

"Reference Handler" daemon prio=10 tid=10409b000 nid=0x10ab25000 in Object.wait() [10ab24000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <7f30011d8> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:485)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:116)
    - locked <7f30011d8> (a java.lang.ref.Reference$Lock)

"main" prio=5 tid=104000800 nid=0x10048d000 runnable [10048a000]
   java.lang.Thread.State: RUNNABLE
    at java.lang.ClassLoader$NativeLibrary.load(Native Method)
    at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1827)
    - locked <7f30010a8> (a java.util.Vector)
    - locked <7f3001100> (a java.util.Vector)
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1724)
    at java.lang.Runtime.loadLibrary0(Runtime.java:823)
    - locked <7f3004e90> (a java.lang.Runtime)
    at java.lang.System.loadLibrary(System.java:1045)
    at sun.security.action.LoadLibraryAction.run(LoadLibraryAction.java:50)
    at java.security.AccessController.doPrivileged(Native Method)
    at sun.awt.NativeLibLoader.loadLibraries(NativeLibLoader.java:38)
    at sun.awt.DebugHelper.<clinit>(DebugHelper.java:29)
    at java.awt.Component.<clinit>(Component.java:566)
    at org.classes.Loader.Load(Loader.java:69)

"VM Thread" prio=9 tid=104096000 nid=0x10aa22000 runnable 

"Gang worker#0 (Parallel GC Threads)" prio=9 tid=104002000 nid=0x103504000 runnable 

"Gang worker#1 (Parallel GC Threads)" prio=9 tid=104002800 nid=0x103607000 runnable 

"Concurrent Mark-Sweep GC Thread" prio=9 tid=10404d000 nid=0x10a6f0000 runnable 
"VM Periodic Task Thread" prio=10 tid=103817800 nid=0x10b23a000 waiting on condition 

"Exception Catcher Thread" prio=10 tid=104001800 nid=0x103401000 runnable 
JNI global references: 913

我真的不知道我还能做什么。也许这是一个愚蠢的错误,但我对这种 Java-C 组合不够熟练,因为这是我第一次看到它。

更新:我已经更新了java代码(感谢trashgod),但它仍然不起作用。 我错过了什么吗?

【问题讨论】:

    标签: java c macos swing java-native-interface


    【解决方案1】:

    按照example,除非您使用 Cocoa,否则您不需要在 C 端使用单独的线程。您确实需要使用invokeLater()event dispatch thread 上构建您的Java GUI。

    【讨论】:

    • 如果我不创建单独的线程,我会收到错误消息:Apple AWT Java VM 已在第一个线程上加载——无法启动 AWT。无法启动 AWT,因为 Java 是在第一个线程上启动的。确保 StartOnFirstThread 未在您的应用程序的 Info.plist 中指定 关于事件调度线程,我昨天已经实现了它,但没有成功。也许我犯了一些错误..我将进一步阅读您发布的链接,然后发布修改后的代码。非常感谢您的回答。
    • 我已经更新了 java 代码,添加了 invokeLater() 但没有任何改变。我是否以错误的方式实现它?
    • 我看不到。我重新运行了我的测试,我收到了一个类似的错误:“Apple AWT Java VM 已在第一个线程上加载——无法启动 AWT。遗憾的是,我没有看到有用的 workaround。任何机会你都可以开始GUI 和然后加载 C 库?
    • 绕过 Apple AWT 错误的唯一方法是在单独的线程上启动 Java VM。这就是我从一开始就做的,并且有效。 Java VM 已正确创建并可以正常执行字节码。我无法解决的是启动 GUI,行为很奇怪。如果我不使用invokeLater(),java 代码会在Frame frame = new Frame(); 之前执行,然后它(可能)会死锁。如果我使用invokeLater()(如上面的代码),则根本不会调用整个Load 方法,并且在调用Load 方法后应用程序(可能)会死锁。
    • 关于加载 C 库的内容,没有任何区别。我什至无法加载它,问题仍然存在。
    【解决方案2】:

    通过查看 Eclipse 项目如何创建其启动器,我能够解决此问题。您需要为 JVM 生成一个单独的线程,但 main 方法需要启动 CFRunLoop。

    对于您的特定实施,可能还有一些额外的细节,但在我们的案例中,类似的东西目前正在发挥作用:

    ...
    #include <CoreServices/CoreServices.h>
    
    static void dummyCallback(void * info) {}
    ...
    
    ...
    if (stack_size > 0) {
        pthread_attr_setstacksize(&thread_attr, stack_size);
    }
    
    CFRunLoopRef loopRef = CFRunLoopGetCurrent();
    
    /* Start the thread that we will start the JVM on. */
    pthread_create(&vmthread, &thread_attr, startJava, (void *)&thread_data_struct);
    pthread_attr_destroy(&thread_attr);
    
    CFRunLoopSourceContext sourceContext = { 
       .version = 0, .info = NULL, .retain = NULL, .release = NULL,
       .copyDescription = NULL, .equal = NULL, .hash = NULL, 
       .schedule = NULL, .cancel = NULL, .perform = &dummyCallback };
    
    CFRunLoopSourceRef sourceRef = CFRunLoopSourceCreate(NULL, 0, &sourceContext);
    CFRunLoopAddSource(loopRef, sourceRef,  kCFRunLoopCommonModes);        
    CFRunLoopRun();
    CFRelease(sourceRef);
    ...
    

    您可以在此处查看 Eclipse 实现:

    http://git.eclipse.org/c/equinox/rt.equinox.framework.git

    【讨论】:

    • 感谢提示,但对应的链接选项是什么?
    • 好的,谢谢,我自己找到了:-framework CoreFoundation
    【解决方案3】:

    我有同样的问题,如果我在 AWT 之前加载我的本机库,那么它就会挂起。解决方案是在加载我的本地库之前加载 AWT 本地库。

    ColorModel.getRGBdefault(); //static code in ColorModel loads AWT native library
    System.loadLibrary("MyLibrary"); //then load your native code
    

    【讨论】:

      【解决方案4】:

      这实际上并没有解决原始发帖人的问题,但我在尝试解决类似问题时找到了他/她的帖子。就我而言,我需要启动一个 c++ 程序,并让它调用一个用 Java 编写的成像库。该库使用了一些 awt 类,所以我看到了死锁问题,即使我没有在 Java 代码中创建 UI。

      另外,我想在不同平台上编译相同的 c++ 代码,所以避免使用 Cocoa。

      因为我不需要创建 Java UI,所以当它从 c++ 代码启动时,我可以将“-Djava.awt.headless=true”作为选项添加到 jvm。

      我想发布这个,以防遇到类似情况的其他人偶然发现这个帖子寻找答案。

      【讨论】:

      • 你能再解释一下吗?
      猜你喜欢
      • 1970-01-01
      • 2014-06-14
      • 2018-07-08
      • 2017-07-31
      • 1970-01-01
      • 2011-12-06
      • 2011-04-17
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多