6

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?

Andrea3000
  • 1,018
  • 1
  • 11
  • 26

4 Answers4

7

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

Scott A Miller
  • 696
  • 5
  • 9
2

I have the same issue, if I load my native library before AWT, then it hangs. The solution is to load AWT native library BEFORE loading my native library.

ColorModel.getRGBdefault(); //static code in ColorModel loads AWT native library
System.loadLibrary("MyLibrary"); //then load your native code
Anthony Ho
  • 121
  • 6
2

Following this example, you don't need a separate thread on the C side unless you're using Cocoa. You do need to construct your Java GUI on the event dispatch thread using invokeLater().

trashgod
  • 203,806
  • 29
  • 246
  • 1,045
  • If I do not create a separe thread I get the error: _Apple AWT Java VM was loaded on first thread -- can't start AWT. Can't start the AWT because Java was started on the first thread. Make sure StartOnFirstThread is not specified in your application's Info.plist_ For what is about event dispatch thread, I have already implemented it yesterday without success. Maybe I made some mistake..I will further read the link you posted and I will post the modified code. Thank you very much for your answer. – Andrea3000 Jan 06 '12 at 07:47
  • I've updated the java code adding invokeLater() but nothing has changed. Have I implemented it in a wrong way? – Andrea3000 Jan 07 '12 at 14:11
  • Not that I can see. I re-ran my test, and I get a similar error: "Apple AWT Java VM was loaded on first thread -- can't start AWT. Sadly, I don't see a useful [workaround](http://www.google.com/search?q=Apple+AWT+Java+VM+was+loaded+on+first+thread+--+can%27t+start+AWT). Any chance you can start the GUI and _then_ load the C library? – trashgod Jan 07 '12 at 15:04
  • The only way to bypass that error about Apple AWT is to launch Java VM on a separate thread. This is what I've done from the beginning and it works. Java VM is created correctly and executes bytecode fine. What I'm not able to solve is starting the GUI and the behavior is very strange. If I don't use `invokeLater()` the java code get executed till before `Frame frame = new Frame();` and then it (probably) deadlocks. If I use `invokeLater()` (as in the code above) the whole `Load` method doesn't get called at all and the app (probably) deadlocks just after invoking the `Load` method. – Andrea3000 Jan 07 '12 at 16:52
  • For what is about loading C library, it doesn't make any difference. I can even not load it and the issue still persists. – Andrea3000 Jan 07 '12 at 16:54
  • Can you abandon starting from C and start the Java GUI application from a shell script or the `JavaApplicationStub`? – trashgod Jan 08 '12 at 03:43
  • No, unfortunately I can't. My project is actually an Objective-C applcation which includes an external C library that, among other things, create the Java GUI and execute some Java bytecode. – Andrea3000 Jan 11 '12 at 22:57
0

This doesn't actually solve the original poster's problem, but I found his/her post while trying to solve a similar problem. In my case, I need to start a c++ program, and have it call an imaging library written in Java. That library uses some awt classes, so I was seeing the deadlock issue, even though I wasn't creating a UI in the Java code.

Also, I want to compile the same c++ code on different platforms, so was avoiding using Cocoa.

Because I don't need to create a Java UI, it worked for me to add "-Djava.awt.headless=true" as an option to the jvm when it's started from the c++ code.

I wanted to post this, in case someone else in a similar situation stumbles upon this post looking for answers.

mbreck
  • 121
  • 1
  • 4