0

Consider the simplest possible example of using JNI consisting of a C file

#include <jni.h>

#include <stdio.h>
#include <syslog.h>

JNIEXPORT void JNICALL Java_org_example_hi_sayHi(JNIEnv*, jobject) {
    puts("hi from native code");
    syslog(LOG_NOTICE, "hi from native code");
} 

and Java file hi.java in the org/example subdirectory:

package org.example;

public class hi {
    // THE LINE REFERENCED BELOW: static { System.loadLibrary("hi"); }
    public native void sayHi();
} 

Compiling and running the following Java program

import org.example.hi;

public class sayhi {
    public static void main(String[] args) {
        System.loadLibrary("hi");
        new hi().sayHi();
    }
} 

works perfectly fine.

However doing the same thing from jshell prompt fails:

$ jshell -v
|  Welcome to JShell -- Version 11.0.8
|  For an introduction type: /help intro

jshell> import org.example.hi;

jshell> System.loadLibrary("hi");

jshell> new hi().sayHi();
|  Exception java.lang.UnsatisfiedLinkError: 'void org.example.hi.sayHi()'
|        at hi.sayHi (Native Method)
|        at (#3:1)

and I don't understand why. Loading the library succeeds, as can be seen by either using a non-existing library name in jshell (this results in an error) or by examining the child java process used by jshell process using lsof: I can see that libhi.so is not used by it before the loadLibrary() call but is used after it.

Moreover, uncommenting the commented out line in hi.java makes it work in jshell too, i.e. if loadLibrary() is executed as part of loading the package and not typed at jshell prompt itself, everything works as expected (except that puts() output is nowhere to be seen, apparently because java stdout is redirected somewhere, but syslog() output can be seen and there is no UnsatisfiedLinkError).

But it seems like doing this in jshell should work too -- yet it doesn't. Does anybody have an explanation for this?

FWIW this is not Linux-specific, it is just simpler to test this under Linux. I observe exactly the same behaviour with Java 12 under Windows too.

VZ.
  • 21,740
  • 3
  • 39
  • 42
  • 1
    If your target is to switch libraries on the fly, it will not work this way. Native libraries are loaded per class loader and are unloaded once all the classes are garbage collected (putting it simple). This has few implications. One of them is that it's hard to keep a track what is really loaded (native symbols) and what is already garbage collected. I suggest different approach: http://jnicookbook.owsiak.org/recipe-No-018/ - this way, you are in charge of native code at all times and you really know what is used by your class. Alternatively: http://jnicookbook.owsiak.org/recipe-no-030/ – Oo.oO Dec 23 '20 at 08:30
  • Thanks, I didn't have any specific target in mind, to be honest, I just wanted to make it easier for the users of the library to experiment with things interactively -- and then interested in understanding why it didn't work when I realized that it didn't. I guess the question really is what class loaders are in play here, i.e. why does jshell use a different one from the child java process? – VZ. Dec 23 '20 at 15:55
  • Well, to be honest I don't play with `jshell` that much and never tried to look into it's implementation. I also have no idea how does `jshell` run scripts - is it the very same `VM` or whether it spawns something behind your back :( – Oo.oO Dec 23 '20 at 17:39
  • FWIW it does spawn a child Java process, which seems to be used for actually executing all Java code typed into jshell. – VZ. Dec 23 '20 at 21:20

0 Answers0