├── Makefile ├── README.md ├── config-dir └── jni-config.json └── src ├── HelloWorld.c ├── HelloWorld.java └── manifest.txt /Makefile: -------------------------------------------------------------------------------- 1 | GRAALVM = $(HOME)/graalvm-ce-19.2.1 2 | 3 | clean: 4 | -rm src/*.class 5 | -rm src/*.h 6 | -rm *.jar 7 | -rm *.so 8 | -rm helloworld 9 | 10 | src/HelloWorld.class: src/HelloWorld.java 11 | javac src/HelloWorld.java 12 | 13 | src/HelloWorld.h: src/HelloWorld.java 14 | cd src && javah -jni HelloWorld 15 | 16 | libHelloWorld.so: src/HelloWorld.h src/HelloWorld.c 17 | gcc -shared -Wall -Werror -I$(JAVA_HOME)/include -I$(JAVA_HOME)/include/linux -o libHelloWorld.so -fPIC src/HelloWorld.c 18 | 19 | HelloWorld.jar: src/HelloWorld.class src/manifest.txt 20 | cd src && jar cfm ../HelloWorld.jar manifest.txt HelloWorld.class 21 | 22 | run-jar: HelloWorld.jar libHelloWorld.so 23 | LD_LIBRARY_PATH=./ java -jar HelloWorld.jar 24 | 25 | helloworld: HelloWorld.jar libHelloWorld.so 26 | $(GRAALVM)/bin/native-image \ 27 | -jar HelloWorld.jar \ 28 | -H:Name=helloworld \ 29 | -H:+ReportExceptionStackTraces \ 30 | -H:ConfigurationFileDirectories=config-dir \ 31 | --initialize-at-build-time \ 32 | --verbose \ 33 | --no-fallback \ 34 | --no-server \ 35 | "-J-Xmx1g" \ 36 | -H:+TraceClassInitialization -H:+PrintClassInitialization 37 | 38 | run-native: helloworld libHelloWorld.so 39 | LD_LIBRARY_PATH=./ ./helloworld 40 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # graal-native-image-jni 2 | 3 | ### Aim 4 | 5 | Try and build the smallest possible JNI example to test GraalVM's native-image JNI support. 6 | 7 | ### Result 8 | 9 | Success. 10 | 11 | ``` 12 | $ ./helloworld 13 | Hello world; this is C talking! 14 | ``` 15 | 16 | ### Insight 17 | 18 | In order for native-image to successfuly load a c library to execute, it must run the `System.loadLibrary()` call at runtime, not at build time. 19 | 20 | ### Method 1: Put loadLibrary in the execution path 21 | 22 | This is the version we have done. By putting loadLibrary inside the `main` method, the library is loaded at run time. With this setup we can compile with `--initialize-at-build-time` and everything will work. 23 | 24 | ### Method 2: Put loadLibrary in static class initializer and use --initialize-at-run-time 25 | 26 | Sometimes you don't have control over where you call loadLibrary from. Often existing code places it in the slasses static initializer block. In this case the library is loaded at build time, but then when the final artifact is run, the linked code cannot be found and the programme crashes with a `java.lang.UnsatisfiedLinkError` exception. 27 | 28 | When you place the loadLibrary call within a static block of a class, you must specify to `native-image` that your class should be initialized at runtime. 29 | 30 | ## Requirements 31 | 32 | * Linux 33 | * GraalVM CE 19.2.1 with native-image tool installed 34 | * Working GNU C compiler 35 | 36 | ## Overview 37 | 38 | `HelloWorld.java` contains HelloWorld class, that calls the native code in `HelloWorld.c` to print output. 39 | 40 | `HelloWorld.c` compiles into `libHelloWorld.so` 41 | 42 | `HelloWorld.class` is built into a jar with a simple manifest. 43 | 44 | ## Build and run a JNI jar 45 | 46 | ``` 47 | $ make run-jar 48 | javac src/HelloWorld.java 49 | cd src && jar cfm ../HelloWorld.jar manifest.txt HelloWorld.class 50 | cd src && javah -jni HelloWorld 51 | gcc -shared -Wall -Werror -I/usr/lib/jvm/java-8-oracle/include -I/usr/lib/jvm/java-8-oracle/include/linux -o libHelloWorld.so -fPIC src/HelloWorld.c 52 | LD_LIBRARY_PATH=./ java -jar HelloWorld.jar 53 | Hello world; this is C talking! 54 | ``` 55 | 56 | ## Build and run a native image 57 | 58 | (you can specify a custom GRAALVM path with `make run-native GRAALVM=/path/to/my/graalvm`) 59 | 60 | ``` 61 | $ make run-native 62 | /home/crispin/graalvm-ce-19.2.1/bin/native-image \ 63 | -jar HelloWorld.jar \ 64 | -H:Name=helloworld \ 65 | -H:+ReportExceptionStackTraces \ 66 | -H:ConfigurationFileDirectories=config-dir \ 67 | --initialize-at-build-time \ 68 | --verbose \ 69 | --no-fallback \ 70 | --no-server \ 71 | "-J-Xmx1g" \ 72 | -H:+TraceClassInitialization -H:+PrintClassInitialization 73 | Executing [ 74 | /home/crispin/graalvm-ce-19.2.1/jre/bin/java \ 75 | -XX:+UnlockExperimentalVMOptions \ 76 | -XX:+EnableJVMCI \ 77 | -Dtruffle.TrustAllTruffleRuntimeProviders=true \ 78 | -Dtruffle.TruffleRuntime=com.oracle.truffle.api.impl.DefaultTruffleRuntime \ 79 | -Dgraalvm.ForcePolyglotInvalid=true \ 80 | -Dgraalvm.locatorDisabled=true \ 81 | -d64 \ 82 | -XX:-UseJVMCIClassLoader \ 83 | -XX:+UseJVMCINativeLibrary \ 84 | -Xss10m \ 85 | -Xms1g \ 86 | -Xmx14g \ 87 | -Duser.country=US \ 88 | -Duser.language=en \ 89 | -Dorg.graalvm.version=19.2.1 \ 90 | -Dorg.graalvm.config=CE \ 91 | -Dcom.oracle.graalvm.isaot=true \ 92 | -Djvmci.class.path.append=/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/graal.jar \ 93 | -javaagent:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/svm.jar \ 94 | -Xmx1g \ 95 | -Xbootclasspath/a:/home/crispin/graalvm-ce-19.2.1/jre/lib/boot/graaljs-scriptengine.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/boot/graal-sdk.jar \ 96 | -cp \ 97 | /home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/svm.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/javacpp.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/svm-llvm.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/graal-llvm.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/llvm-platform-specific.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/llvm-wrapper.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/objectfile.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/pointsto.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/jvmci-hotspot.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/graal-management.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/jvmci-api.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/graal.jar \ 98 | com.oracle.svm.hosted.NativeImageGeneratorRunner \ 99 | -watchpid \ 100 | 10964 \ 101 | -imagecp \ 102 | /home/crispin/graalvm-ce-19.2.1/jre/lib/boot/graaljs-scriptengine.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/boot/graal-sdk.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/svm.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/javacpp.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/svm-llvm.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/graal-llvm.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/llvm-platform-specific.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/llvm-wrapper.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/objectfile.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/builder/pointsto.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/jvmci-hotspot.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/graal-management.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/jvmci-api.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/jvmci/graal.jar:/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/library-support.jar:/home/crispin/dev/epiccastle/graal-native-image-jni/HelloWorld.jar \ 103 | -H:Path=/home/crispin/dev/epiccastle/graal-native-image-jni \ 104 | -H:Class=HelloWorld \ 105 | -H:+ReportExceptionStackTraces \ 106 | -H:ConfigurationFileDirectories=config-dir \ 107 | -H:ClassInitialization=:build_time \ 108 | -H:FallbackThreshold=0 \ 109 | -H:+TraceClassInitialization \ 110 | -H:+PrintClassInitialization \ 111 | -H:CLibraryPath=/home/crispin/graalvm-ce-19.2.1/jre/lib/svm/clibraries/linux-amd64 \ 112 | -H:Name=helloworld 113 | ] 114 | [helloworld:10986] classlist: 1,616.92 ms 115 | [helloworld:10986] (cap): 1,526.85 ms 116 | [helloworld:10986] setup: 2,825.72 ms 117 | [helloworld:10986] (typeflow): 5,943.24 ms 118 | [helloworld:10986] (objects): 4,215.63 ms 119 | [helloworld:10986] (features): 329.14 ms 120 | [helloworld:10986] analysis: 10,674.16 ms 121 | Printing initializer configuration to /home/crispin/dev/epiccastle/graal-native-image-jni/reports/initializer_configuration_20191115_205814.txt 122 | Printing initializer dependencies to /home/crispin/dev/epiccastle/graal-native-image-jni/reports/initializer_dependencies_20191115_205814.dot 123 | Printing 0 classes that are considered as safe for build-time initialization to /home/crispin/dev/epiccastle/graal-native-image-jni/reports/safe_classes_20191115_205814.txt 124 | Printing 2599 classes of type BUILD_TIME to /home/crispin/dev/epiccastle/graal-native-image-jni/reports/build_time_classes_20191115_205814.txt 125 | Printing 13 classes of type RERUN to /home/crispin/dev/epiccastle/graal-native-image-jni/reports/rerun_classes_20191115_205814.txt 126 | Printing 0 classes of type RUN_TIME to /home/crispin/dev/epiccastle/graal-native-image-jni/reports/run_time_classes_20191115_205814.txt 127 | [helloworld:10986] (clinit): 225.64 ms 128 | [helloworld:10986] universe: 593.86 ms 129 | [helloworld:10986] (parse): 896.34 ms 130 | [helloworld:10986] (inline): 1,534.65 ms 131 | [helloworld:10986] (compile): 7,715.71 ms 132 | [helloworld:10986] compile: 10,785.92 ms 133 | [helloworld:10986] image: 920.97 ms 134 | [helloworld:10986] write: 123.24 ms 135 | [helloworld:10986] [total]: 27,737.71 ms 136 | LD_LIBRARY_PATH=./ ./helloworld 137 | Hello world; this is C talking! 138 | ``` 139 | -------------------------------------------------------------------------------- /config-dir/jni-config.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "HelloWorld", 4 | "allDeclaredConstructors" : true, 5 | "allPublicConstructors" : true, 6 | "allDeclaredMethods" : true, 7 | "allPublicMethods" : true, 8 | "allDeclaredClasses" : true, 9 | "allPublicClasses" : true 10 | } 11 | ] 12 | -------------------------------------------------------------------------------- /src/HelloWorld.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "HelloWorld.h" 4 | 5 | JNIEXPORT void JNICALL 6 | Java_HelloWorld_print(JNIEnv *env, jobject obj) { 7 | 8 | printf("Hello world; this is C talking!\n"); 9 | return; 10 | } 11 | -------------------------------------------------------------------------------- /src/HelloWorld.java: -------------------------------------------------------------------------------- 1 | class HelloWorld { 2 | // putting System.loadLibrary() here forces us 3 | // to mark this class to initialize at runtime 4 | // when building with native-image 5 | // https://github.com/oracle/graal/issues/1828 6 | // 7 | // static { 8 | // System.loadLibrary("HelloWorld"); 9 | // } 10 | 11 | private native void print(); 12 | 13 | // entry point 14 | public static void main(String[] args) { 15 | 16 | // instead we System.loadLibrary() inside the execution path 17 | // to load the library file 18 | System.loadLibrary("HelloWorld"); 19 | 20 | new HelloWorld().print(); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /src/manifest.txt: -------------------------------------------------------------------------------- 1 | Manifest-Version: 1.0 2 | Created-By: 1.8.0_201 (Oracle Corporation) 3 | Main-Class: HelloWorld 4 | --------------------------------------------------------------------------------