性能问答>用 JNI 创建 JFrame 的死锁>
3回复
3年前

用 JNI 创建 JFrame 的死锁



我有一个需要从C++调用的Java库,我用JNI调用了我需要的方法,但是出现了一个陷入奇怪死锁的方法。

这个有问题的方法在工作线程上创建了一个JFrame,从C++调用时它不返回。我谷歌了一下,我发现一篇文章里面写,在MacOS里,用JNI的时候,任何跟Swing/awt的交互都不能从主线程完成。我改成从工作线程调用该方法之后,jvm还是卡在死锁中并且JNI调用还是不返回。

我的代码大概是这样(简化了一下但基本相同):
Java代码:

import javax.swing.*;
import java.awt.*;

public class MyClass
{
    private JFrame frame;
    private Thread thread;
    public void createFrame()
    {
        thread = new Thread(() -> {
            frame = new  JFrame("My Frame");
            frame.setTitle("JFrameCenter Position");
            frame.add(new JLabel("JFrame set to center of the screen", SwingConstants.CENTER), BorderLayout.CENTER);
            frame.setSize(400, 300);
            frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            frame.setLocationRelativeTo(null); 
            frame.setVisible(true);
        });
        thread.start();
        try {
            thread.join();
        } catch (InterruptedException e) {
            System.out.println("Exception on join Thread");
            e.printStackTrace();
        }
    }
}

C++代码:

#include <iostream>
#include <jni.h>
#include <dlfcn.h>

#define JLI_DYLIB "/lib/jli/libjli.dylib"
#define JNI_CREATEVM_FUNC "JNI_CreateJavaVM"
typedef jint (JNICALL CreateJavaVM_t)(JavaVM **pvm, void **env, void *args);

void* DyLibHandler;
JavaVM *jvm;



void *GetStartJvmFunc(std::string javaHome) {
    std::string jliPath = javaHome.append(JLI_DYLIB);
    DyLibHandler = dlopen(jliPath.c_str(), RTLD_LAZY);
    if(!DyLibHandler)
    {
        return NULL;
    }
    void * jvmFunc = dlsym(DyLibHandler, JNI_CREATEVM_FUNC);
    if  (!jvmFunc)
    {
        dlclose(DyLibHandler);
        return NULL;
    }
    
    return jvmFunc;
}

void StartJvm(const char * javaHome, const char * classPath) {
    std::string jarsPath = "-Djava.class.path=";
    std::string home = "-Djava.home=";
    JavaVMOption* options = new JavaVMOption[3];   // JVM invocation options
    options[0].optionString = (char*)jarsPath.append(classPath).c_str();
    options[0].extraInfo = NULL;
    options[1].optionString = (char*)home.append(javaHome).c_str();
    options[1].extraInfo = NULL;
    options[2].optionString = "-Dsun.net.inetaddr.ttl=10";
    options[2].extraInfo = NULL;

    JavaVMInitArgs vm_args;
    vm_args.options = options;
    vm_args.nOptions = 3;
    vm_args.version = JNI_VERSION_1_8;
    
    JNIEnv *jniEnvironment;
    CreateJavaVM_t* createVMFunc = (CreateJavaVM_t*)GetStartJvmFunc(std::string(javaHome));
    jint rc = createVMFunc(&jvm, (void**)&jniEnvironment, &vm_args);
    delete[] options;
    if (rc != JNI_OK) {
        createVMFunc = NULL;
        exit(EXIT_FAILURE);
    }
}

void CreateJFrame(JNIEnv *jniEnvironment)
{
    jclass localHandler = jniEnvironment->FindClass("MyClass");
    if (!localHandler)
    {
        std::cout << "Could not Find LocalClass" << std::endl;
        exit(EXIT_FAILURE);
    }
    
    jclass classHandle = (jclass) jniEnvironment->NewGlobalRef(localHandler);
    jniEnvironment->DeleteLocalRef(localHandler);
    
    jmethodID constructorHandler = jniEnvironment->GetMethodID(classHandle, "<init>", "()V");
    if (!constructorHandler)
    {
        std::cout << "Could not find Constructor" << std::endl;
        exit(EXIT_FAILURE);
    }

    jmethodID createMethodHandler = jniEnvironment->GetMethodID(classHandle, "createFrame", "()V");
    if (!createMethodHandler)
    {
        std::cout << "Could not Find CreateFrame Method" << std::endl;
        exit(EXIT_FAILURE);
    }
    
    jobject localRef = jniEnvironment->NewObject(classHandle, constructorHandler);
    if(!localRef)
    {
        std::cout << "Could not instantiate MyClass" << std::endl;
        exit(EXIT_FAILURE);
    }

    jobject classInstance = jniEnvironment->NewGlobalRef(localRef);
    jniEnvironment->DeleteLocalRef(localRef);
    if (classInstance == nullptr)
    {
        std::cout << "Could not create global ref to MyClass instance" << std::endl;
        exit(EXIT_FAILURE);
    }
    
    jniEnvironment->CallVoidMethod(classInstance, createMethodHandler);
}

void SourceCallback (  void *info  ) {
    
    std::cout << "From Source Callback! Got Event" << std::endl;
}

void * ThreadProc(void * arg)
{
    JNIEnv *env = NULL;
    jint result =jvm->AttachCurrentThread((void **)&env, NULL);
    if( result != JNI_OK)
    {
        std::cout << "Error! Could not attach JNIEnv to thread (" << result << ")"<< std::endl;
        goto clean;
    }
    
    CreateJFrame(env);
    
    std::cout << "Deataching Thread" << std::endl;
    result = jvm->DetachCurrentThread();
    if(result != JNI_OK)
    {
        std::cout << "Error! could not deatached current thread (" << result << ")" << std::endl;
    }
    else{
        std::cout << "Done Deataching Thread" << std::endl;
    }
    
clean:
    std::cout << "Done Creating Frame" << std::endl;
    return NULL;
}

int main(int argc, const char * argv[])
{
    StartJvm(/*Java_Home path*/, /*Path to MyClass.class*/);
    pthread_t vmThread;
    pthread_create(&vmThread, NULL, ThreadProc, NULL);
    pthread_join(vmThread, NULL);
    std::cout << "UIThread is done" << std::endl;
    std::cout << "Waiting to Terminate program" << std::endl;
    std::string str;
    std::getline(std::cin, str);
}

我想用Jstack看下为什么会导致死锁,但我不确定是什么原因。 这是 JStack:

Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.291-b10 mixed mode):

"Thread-1" #11 prio=5 os_prio=31 tid=0x00007f88a70b0800 nid=0x3e03 runnable [0x0000700002a51000]
   java.lang.Thread.State: RUNNABLE
    at java.lang.ClassLoader$NativeLibrary.load(Native Method)
    at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1934)
    - locked <0x000000076ab068b0> (a java.util.Vector)
    - locked <0x000000076ab06908> (a java.util.Vector)
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1817)
    at java.lang.Runtime.load0(Runtime.java:810)
    - locked <0x000000076ab1abb0> (a java.lang.Runtime)
    at java.lang.System.load(System.java:1086)
    at java.lang.ClassLoader$NativeLibrary.load(Native Method)
    at java.lang.ClassLoader.loadLibrary0(ClassLoader.java:1934)
    - locked <0x000000076ab068b0> (a java.util.Vector)
    - locked <0x000000076ab06908> (a java.util.Vector)
    at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1838)
    at java.lang.Runtime.loadLibrary0(Runtime.java:871)
    - locked <0x000000076ab1abb0> (a java.lang.Runtime)
    at java.lang.System.loadLibrary(System.java:1122)
    at java.awt.Toolkit$3.run(Toolkit.java:1636)
    at java.awt.Toolkit$3.run(Toolkit.java:1634)
    at java.security.AccessController.doPrivileged(Native Method)
    at java.awt.Toolkit.loadLibraries(Toolkit.java:1633)
    at java.awt.Toolkit.<clinit>(Toolkit.java:1670)
    at java.awt.Component.<clinit>(Component.java:593)
    at MyClass.lambda$createFrame$0(MyClass.java:11)
    at MyClass$$Lambda$1/1923699808.run(Unknown Source)
    at java.lang.Thread.run(Thread.java:748)

"Thread-0" #10 prio=5 os_prio=31 tid=0x00007f88a804d000 nid=0x4003 in Object.wait() [0x0000700002951000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076adaaa58> (a java.lang.Thread)
    at java.lang.Thread.join(Thread.java:1252)
    - locked <0x000000076adaaa58> (a java.lang.Thread)
    at java.lang.Thread.join(Thread.java:1326)
    at MyClass.createFrame(MyClass.java:21)

"Service Thread" #9 daemon prio=9 os_prio=31 tid=0x00007f88a7010000 nid=0x4203 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #8 daemon prio=9 os_prio=31 tid=0x00007f88a8034800 nid=0x4403 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #7 daemon prio=9 os_prio=31 tid=0x00007f88a7017800 nid=0x4603 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #6 daemon prio=9 os_prio=31 tid=0x00007f88a7829800 nid=0x3503 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #5 daemon prio=9 os_prio=31 tid=0x00007f88a7016800 nid=0x4903 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=31 tid=0x00007f88a8008800 nid=0x3303 waiting on condition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=31 tid=0x00007f88a8013000 nid=0x5203 in Object.wait() [0x00007000020b3000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076ab08ee0> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
    - locked <0x000000076ab08ee0> (a java.lang.ref.ReferenceQueue$Lock)
    at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
    at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=31 tid=0x00007f88a8010000 nid=0x5403 in Object.wait() [0x0000700001fb0000]
   java.lang.Thread.State: WAITING (on object monitor)
    at java.lang.Object.wait(Native Method)
    - waiting on <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)
    at java.lang.Object.wait(Object.java:502)
    at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
    - locked <0x000000076ab06c00> (a java.lang.ref.Reference$Lock)
    at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"main" #1 prio=5 os_prio=31 tid=0x00007f88a9008800 nid=0x307 runnable [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"VM Thread" os_prio=31 tid=0x00007f88a800b800 nid=0x2a03 runnable

"GC task thread#0 (ParallelGC)" os_prio=31 tid=0x00007f88a780c800 nid=0x2707 runnable

"GC task thread#1 (ParallelGC)" os_prio=31 tid=0x00007f88a780d800 nid=0x2503 runnable

"GC task thread#2 (ParallelGC)" os_prio=31 tid=0x00007f88a9808800 nid=0x2403 runnable

"GC task thread#3 (ParallelGC)" os_prio=31 tid=0x00007f88a7015800 nid=0x2203 runnable

"GC task thread#4 (ParallelGC)" os_prio=31 tid=0x00007f88aa008800 nid=0x2003 runnable

"GC task thread#5 (ParallelGC)" os_prio=31 tid=0x00007f88aa808800 nid=0x1a03 runnable

"GC task thread#6 (ParallelGC)" os_prio=31 tid=0x00007f88a7016000 nid=0x1c03 runnable

"GC task thread#7 (ParallelGC)" os_prio=31 tid=0x00007f88a8009800 nid=0x1d03 runnable

"VM Periodic Task Thread" os_prio=31 tid=0x00007f88a783c800 nid=0x4103 waiting on condition

JNI global references: 317

Heap
 PSYoungGen      total 76288K, used 7868K [0x000000076ab00000, 0x0000000770000000, 0x00000007c0000000)
  eden space 65536K, 12% used [0x000000076ab00000,0x000000076b2af378,0x000000076eb00000)
  from space 10752K, 0% used [0x000000076f580000,0x000000076f580000,0x0000000770000000)
  to   space 10752K, 0% used [0x000000076eb00000,0x000000076eb00000,0x000000076f580000)
 ParOldGen       total 175104K, used 0K [0x00000006c0000000, 0x00000006cab00000, 0x000000076ab00000)
  object space 175104K, 0% used [0x00000006c0000000,0x00000006c0000000,0x00000006cab00000)
 Metaspace       used 4266K, capacity 4704K, committed 4992K, reserved 1056768K
  class space    used 474K, capacity 525K, committed 640K, reserved 1048576K

我看到Thread-0在等待调用Thread.join,但是工作线程 (Thread-1) 正在等待加载本机库(java.lang.ClassLoader$NativeLibrary.load(Native Method) ) ,但这并没有发生,程序只是挂在那里,直到我终止进程。

我想通过前面的代码显示JFrame,在控制台查看最后两个打印,还有等待控制台中的输入终止的程序。但是就像我之前所说的那样,既没有显示 JFrame,也没有发生打印。

请问有大神知道为什么会发生这种情况吗?

3057 阅读
请先登录,查看3条精彩评论吧
快去登录吧,你将获得
  • 浏览更多精彩评论
  • 和开发者讨论交流,共同进步