I have a Java library that I need to call from C++. Using JNI I managed to call the methods that I needed successfully, except for one that gets stuck on a weird deadlock.
That problematic method creates a JFrame on a worker thread, and when called from C++ it never returns. After some googling I came across with a post that stated that in MacOS any interaction with swing/awt, when using JNI, must not be done from the main thread. After changing the code to call that method from a worker thread, the jvm still gets stuck on a deadlock and the JNI call never returns.
Currently my code looks like this (Simplified but essentially the same):
Java Code:
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++ code
#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);
}
I took a Jstack of the process, hoping to get an idea of what could be causing the deadlock, but I can't identify what could be the reason. Here is the 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
I see that Thread-0 is waiting on the call Thread.join which is fine, but the worker thread (Thread-1) is waiting for a native library to be loaded (java.lang.ClassLoader$NativeLibrary.load(Native Method)) and that never happens, the program just hangs in there until I kill the process.
With the previous code I would expect, the JFrame to be displayed, to see the last two prints on the console, and the program to wait for an input in the console to terminate. But as I said before the C++ program gets stuck waiting for the java method to return, and neither the JFrame gets displayed nor the prints happened. Does anyone have an idea why could this be happening?