Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

OSX: JavaVM, AWT/Swing and possibly a deadlock

I'm really new to java programming therefore I apologise in advance if this sounds like a stupid question.

I'm trying to build a simple application written in plain C, which must create a JavaVM and then create a new window by loading java code based on AWT/Swing.

Following this technical note I'v learned that in Mac OSX only, JavaVM must be invoked from a thread different from the main thread in order to be able to create a GUI based on AWT.

Therefore, in the main function of my C application I created a new thread that performs everything, from the creation of the javaVM to the creation of the GUI.

Since the application isn't in reality so simple, I will post a simplified version.

main function:

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;
}

Thread function:

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 code: (UPDATED)

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);

              }
       });
     }
}

All of the above code executes successfully except for the creation of the window which causes a deadlock or something similar since terminal remains busy without any CPU usage and both threads remain alive.

If I comment out the lines concerning the creation of the window, the application execute successfully and quit.

This is the output from 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

I really don't know what more can I do. Maybe it's a stupid mistake but I'm not skilled enough with this Java-C mix since it's the first time that I'm looking at it.

UPDATE: I have updated the java code (thanks to trashgod) but it still doesn't work. Am I missing something?

like image 210
Andrea3000 Avatar asked Jan 05 '12 22:01

Andrea3000


1 Answers

I was able to resolve this issue by looking at how the Eclipse project creates its launchers. You need to spawn a separate thread for the JVM as you have done, but the main method needs to start the CFRunLoop.

There might be some additional details for your particular implementation, but something similar to this is currently working in our case:

...
#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);
...

You can look through the Eclipse implementation here:

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

like image 168
Scott A Miller Avatar answered Oct 11 '22 06:10

Scott A Miller