1

I made a Java program which calls JNI, and in JNI it creates a "JNICallback" thread and executes in it env-attachCurrentThreadAsDaemon()

My goal is to notify from JNICallback thread, the another pure Java thread. I do standard things like synchronized -> wait, synchronized -> notifyAll, using static final Object as a sync. If I run it under Eclipse, everything works fine.

But if I export it as a "runnable jar file" and run under same JVM, I see my JNI program invokes monitorEnter, notifyAll, monitorExit with no errors, but Java doesnt notified. In the debugger I see different ObjectId's for the very same object used from JNI and from Java, this is suspicious for me (it is names onConnectEvent in the code below) I even move notification code from JNI to Java, and use a static void method call from JNI, doing synchronize->notifyAll in Java, but this only shows me that waiting thread waits on different ObjectId, so I guess this is the reason.

...
    static final Object onConnectEvent = new Object();
...
    Thread dogServerConnect,...;
...
    public MainClass(){
        dogServerConnect = runDog(new ServerConnectionWatchDog(), "onServerConnect");
        someJNIstuffPlusNewThreadsCreation();
...
    
    static void onJNICallbackEvent(int type) {
        switch (type) {
        case 1 -> {
            synchronized(onConnectEvent) {
                onConnectEvent.notifyAll();
                 ^^^^^ got to this line from JNI, just fine
            }
...
    class ServerConnectionWatchDog implements Runnable {
        @Override
        public void run() {
            while(true)
                synchronized(onConnectEvent) {
                    try {
                        onConnectEvent.wait();
                        for (var l : listeners) {
^^^^^^^^^^^^^^^^^^^^^^^^^ never come to this line
...

again, this is really works well under Eclipse, but for some reason fails as separate jar file. Why could same final static object have a different instance ids for different threads, or it's just fine nowadays?

I mean for the waiting thread Eclipse shows me "waiting for: Object (id=48)" and for the native thread on before "notifyAll" execution it shows me "owns: Object (id=50)". Maybe this is because I manually pause the waiting thread, to see object id (then run it again, this doesn't the cause) What could I miss?

Павел
  • 677
  • 6
  • 21
  • openjdk version "16.0.1" 2021-04-20 – Павел Nov 08 '21 at 12:18
  • sounds like a "path/linking issue"!(?) ("runs in eclipse, but not outside") How do you do/who does the "compiling & linking"? (https://www.baeldung.com/jni#3-compiling-and-linking) – xerx593 Nov 08 '21 at 12:19
  • Just to check off a few things: Are you sure that `listeners` is not empty, `onConnectEvent.wait();` is called before `onConnectEvent.notifyAll();` and there are not multiple `ClassLoader`s involved? Also, what threads access `listeners`? Is `listeners` thread-safe? – dan1st Nov 08 '21 at 12:40
  • I don't understand why you talk about linking. – Павел Nov 08 '21 at 12:41
  • @dan1st, no, the problem is indeed the threads looking into same static final Object see different instances, I am not kidding. And again, this works under eclipse, and not if I export it as "runnable jar file" – Павел Nov 08 '21 at 12:42
  • @dan1st, actually I keep calling the same method multiple times, and yes, wait is in wait before botifyall is called. The problem is with some classloaders mb, or mb some modern jvm isolation – Павел Nov 08 '21 at 12:44
  • @dan1st I checked this, put System.out.println into wait and notify methods, the output is "onJNICallbackEvent >>>>>>>>>>>> 'java.lang.Object@5875bee' class java.lang.Object null" "ServerConnectionWatchDog >>>>>>>>>>>> 'java.lang.Object@74e36316' class java.lang.Object null" - there null refers to classloader – Павел Nov 08 '21 at 12:46
  • Can you try moving `onConnectEvent` to another class and providing a public getter? – dan1st Nov 08 '21 at 12:49
  • Does [this classloader-independent singleton approach](https://stackoverflow.com/a/47445573/10871900) work (second answer)? – dan1st Nov 08 '21 at 12:51
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/238989/discussion-between--and-dan1st). – Павел Nov 08 '21 at 12:51
  • I unzipped runnable jar file created by eclise and run it via java -cp .;./* mainclass this way it works as required. So this might be the feature of Eclipse jar classloader. I would appreciate if somebody could provide the way to deal with this issue if I want to continue with Eclipse – Павел Nov 08 '21 at 13:10
  • 2
    I was able to workaround the problem by passing reference to the java object for syncronization from java to jni then use them via global references. this leaves the classloading problem behind. during the testing I indeed found that my classes created twice, and JNI threads uses their separated classloaders, even if they attached to the JVM. I was unsuccessfull in dealing with classloaders, so insted used live objects to pass the information/referencecs. – Павел Nov 08 '21 at 19:04

1 Answers1

0

Found the answer in the FAQ for Android: why didn't FindClass find my class. Although my environment is not Android, it still makes sence. The thing is that env->FindClass unwinds a Java stack to find a caller class and use it's classloader. And the Java stack is empty for the native threads which started in JNI. So this gives us an only options either get a global ref to the required classes before creating the native thread, or to get in the same place a reference to it's classloader and then use a FindClass from saved classLoader, which is harder. This solution prevents the Eclipse JarRsrcLoader from creation of duplicate static objects. Probably one might say that doing env->FindClass in newly created native thread in JNI is a bad design, at least use it with caution.

Павел
  • 677
  • 6
  • 21
  • btw this doesn't answer why it works without Eclipse jar loader. probably some recent jdk tricks, which eclipse jar loader is not aware of. – Павел Nov 18 '21 at 07:00