From 75a8b7fa831ec22a149f271c119a2496c1c5ee87 Mon Sep 17 00:00:00 2001 From: Mandy Chung Date: Mon, 23 Mar 2020 09:05:39 -0700 Subject: [PATCH] 8240975: Extend NativeLibraries to support explicit unloading Reviewed-by: alanb, mcimadamore --- .../share/classes/java/lang/ClassLoader.java | 2 +- .../jdk/internal/loader/BootLoader.java | 2 +- .../jdk/internal/loader/NativeLibraries.java | 157 ++++++++++++++---- .../jdk/internal/loader/NativeLibrary.java | 7 +- .../share/native/libjava/NativeLibraries.c | 104 ++++++------ .../internal/loader/NativeLibraries/Main.java | 70 ++++++++ .../internal/loader/NativeLibrariesTest.java | 135 +++++++++++++++ .../NativeLibraries/libnativeLibrariesTest.c | 65 ++++++++ .../loader/NativeLibraries/p/Test.java | 37 +++++ 9 files changed, 490 insertions(+), 89 deletions(-) create mode 100644 test/jdk/jdk/internal/loader/NativeLibraries/Main.java create mode 100644 test/jdk/jdk/internal/loader/NativeLibraries/java.base/jdk/internal/loader/NativeLibrariesTest.java create mode 100644 test/jdk/jdk/internal/loader/NativeLibraries/libnativeLibrariesTest.c create mode 100644 test/jdk/jdk/internal/loader/NativeLibraries/p/Test.java diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java index 0f6ed3579bb..7e8aaaee794 100644 --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -2374,7 +2374,7 @@ protected String findLibrary(String libname) { return null; } - private final NativeLibraries libraries = new NativeLibraries(this); + private final NativeLibraries libraries = NativeLibraries.jniNativeLibraries(this); // Invoked in the java.lang.Runtime class to implement load and loadLibrary. static NativeLibrary loadLibrary(Class fromClass, File file) { diff --git a/src/java.base/share/classes/jdk/internal/loader/BootLoader.java b/src/java.base/share/classes/jdk/internal/loader/BootLoader.java index d05076c76e4..2a8d7c4e7b4 100644 --- a/src/java.base/share/classes/jdk/internal/loader/BootLoader.java +++ b/src/java.base/share/classes/jdk/internal/loader/BootLoader.java @@ -73,7 +73,7 @@ private BootLoader() { } // native libraries loaded by the boot class loader private static final NativeLibraries NATIVE_LIBS - = new NativeLibraries(null); + = NativeLibraries.jniNativeLibraries(null); /** * Returns the unnamed module for the boot loader. diff --git a/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java b/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java index 3dc87fd53ab..b27c37dd052 100644 --- a/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java +++ b/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java @@ -24,6 +24,7 @@ */ package jdk.internal.loader; +import jdk.internal.misc.VM; import jdk.internal.ref.CleanerFactory; import jdk.internal.util.StaticProperty; @@ -34,6 +35,7 @@ import java.util.ArrayDeque; import java.util.Deque; import java.util.HashSet; +import java.util.Objects; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; @@ -52,24 +54,73 @@ */ public final class NativeLibraries { - private final Map libraries = new ConcurrentHashMap<>(); + private final Map libraries = new ConcurrentHashMap<>(); private final ClassLoader loader; - private final Class caller; // may be null. If not null, this is used as - // fromClass as a fast-path. See loadLibrary(String name). + // caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary + // unless specified + private final Class caller; // may be null private final boolean searchJavaLibraryPath; + // loading JNI native libraries + private final boolean isJNI; - public NativeLibraries(ClassLoader loader) { + /** + * Creates a NativeLibraries instance for loading JNI native libraries + * via for System::loadLibrary use. + * + * 1. Support of auto-unloading. The loaded native libraries are unloaded + * when the class loader is reclaimed. + * 2. Support of linking of native method. See JNI spec. + * 3. Restriction on a native library that can only be loaded by one class loader. + * Each class loader manages its own set of native libraries. + * The same JNI native library cannot be loaded into more than one class loader. + * + * This static factory method is intended only for System::loadLibrary use. + * + * @see + * JNI Specification: Library and Version Management + */ + public static NativeLibraries jniNativeLibraries(ClassLoader loader) { + return new NativeLibraries(loader); + } + + /** + * Creates a raw NativeLibraries instance that has the following properties: + * 1. Native libraries loaded in this raw NativeLibraries instance are + * not JNI native libraries. Hence JNI_OnLoad and JNI_OnUnload will + * be ignored. No support for linking of native method. + * 2. Native libraries not auto-unloaded. They may be explicitly unloaded + * via NativeLibraries::unload. + * 3. No relationship with class loaders. + * + * This static factory method is restricted for JDK trusted class use. + */ + public static NativeLibraries rawNativeLibraries(Class trustedCaller, + boolean searchJavaLibraryPath) { + return new NativeLibraries(trustedCaller, searchJavaLibraryPath); + } + + private NativeLibraries(ClassLoader loader) { // for null loader, default the caller to this class and // do not search java.library.path - this(loader, loader != null ? null : NativeLibraries.class, loader != null ? true : false); + this.loader = loader; + this.caller = loader != null ? null : NativeLibraries.class; + this.searchJavaLibraryPath = loader != null ? true : false; + this.isJNI = true; } - public NativeLibraries(ClassLoader loader, Class caller, boolean searchJavaLibraryPath) { - if (caller != null && caller.getClassLoader() != loader) { - throw new IllegalArgumentException(caller.getName() + " must be defined by " + loader); + + /* + * Constructs a NativeLibraries instance of no relationship with class loaders + * and disabled auto unloading. + */ + private NativeLibraries(Class caller, boolean searchJavaLibraryPath) { + Objects.requireNonNull(caller); + if (!VM.isSystemDomainLoader(caller.getClassLoader())) { + throw new IllegalArgumentException("must be JDK trusted class"); } - this.loader = loader; + this.loader = caller.getClassLoader(); this.caller = caller; this.searchJavaLibraryPath = searchJavaLibraryPath; + this.isJNI = false; } /* @@ -169,11 +220,26 @@ private NativeLibrary loadLibrary(Class fromClass, String name, boolean isBui } } - NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin); + NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin, isJNI); // load the native library nativeLibraryContext.push(lib); try { - if (!lib.open()) return null; + if (!lib.open()) { + return null; // fail to open the native library + } + // auto unloading is only supported for JNI native libraries + // loaded by custom class loaders that can be unloaded. + // built-in class loaders are never unloaded. + boolean autoUnload = isJNI && !VM.isSystemDomainLoader(loader) + && loader != ClassLoaders.appClassLoader(); + if (autoUnload) { + // register the loaded native library for auto unloading + // when the class loader is reclaimed, all native libraries + // loaded that class loader will be unloaded. + // The entries in the libraries map are not removed since + // the entire map will be reclaimed altogether. + CleanerFactory.cleaner().register(loader, lib.unloader()); + } } finally { nativeLibraryContext.pop(); } @@ -218,6 +284,26 @@ public NativeLibrary loadLibrary(Class fromClass, String name) { return lib; } + /** + * Unloads the given native library + * + * @param lib native library + */ + public void unload(NativeLibrary lib) { + if (isJNI) { + throw new UnsupportedOperationException("explicit unloading cannot be used with auto unloading"); + } + Objects.requireNonNull(lib); + synchronized (loadedLibraryNames) { + NativeLibraryImpl nl = libraries.remove(lib.name()); + if (nl != lib) { + throw new IllegalArgumentException(lib.name() + " not loaded by this NativeLibraries instance"); + } + // unload the native library and also remove from the global name registry + nl.unloader().run(); + } + } + private NativeLibrary findFromPaths(String[] paths, Class fromClass, String name) { for (String path : paths) { File libfile = new File(path, System.mapLibraryName(name)); @@ -255,16 +341,21 @@ static class NativeLibraryImpl implements NativeLibrary { final String name; // Indicates if the native library is linked into the VM final boolean isBuiltin; + // Indicate if this is JNI native library + final boolean isJNI; // opaque handle to native library, used in native code. long handle; // the version of JNI environment the native library requires. int jniVersion; - NativeLibraryImpl(Class fromClass, String name, boolean isBuiltin) { + NativeLibraryImpl(Class fromClass, String name, boolean isBuiltin, boolean isJNI) { + assert !isBuiltin || isJNI : "a builtin native library must be JNI library"; + this.fromClass = fromClass; this.name = name; this.isBuiltin = isBuiltin; + this.isJNI = isJNI; } @Override @@ -277,26 +368,19 @@ public long find(String name) { return findEntry0(this, name); } + Runnable unloader() { + return new Unloader(name, handle, isBuiltin, isJNI); + } + /* - * Loads the native library and registers for cleanup when its - * associated class loader is unloaded + * Loads the named native library */ boolean open() { if (handle != 0) { throw new InternalError("Native library " + name + " has been loaded"); } - if (!load(this, name, isBuiltin)) return false; - - // register the class loader for cleanup when unloaded - // builtin class loaders are never unloaded - ClassLoader loader = fromClass != null ? fromClass.getClassLoader() : null; - if (loader != null && - loader != ClassLoaders.platformClassLoader() && - loader != ClassLoaders.appClassLoader()) { - CleanerFactory.cleaner().register(loader, new Unloader(name, handle, isBuiltin)); - } - return true; + return load(this, name, isBuiltin, isJNI); } } @@ -308,13 +392,15 @@ static class Unloader implements Runnable { // This represents the context when a native library is unloaded // and getFromClass() will return null, static final NativeLibraryImpl UNLOADER = - new NativeLibraryImpl(null, "dummy", false); + new NativeLibraryImpl(null, "dummy", false, false); final String name; final long handle; final boolean isBuiltin; + final boolean isJNI; - Unloader(String name, long handle, boolean isBuiltin) { + Unloader(String name, long handle, boolean isBuiltin, boolean isJNI) { + assert !isBuiltin || isJNI : "a builtin native library must be JNI library"; if (handle == 0) { throw new IllegalArgumentException( "Invalid handle for native library " + name); @@ -323,18 +409,21 @@ static class Unloader implements Runnable { this.name = name; this.handle = handle; this.isBuiltin = isBuiltin; + this.isJNI = isJNI; } @Override public void run() { - synchronized (NativeLibraries.loadedLibraryNames) { + synchronized (loadedLibraryNames) { /* remove the native library name */ - NativeLibraries.loadedLibraryNames.remove(name); - NativeLibraries.nativeLibraryContext.push(UNLOADER); + if (!loadedLibraryNames.remove(name)) { + throw new IllegalStateException(name + " has already been unloaded"); + } + nativeLibraryContext.push(UNLOADER); try { - unload(name, isBuiltin, handle); + unload(name, isBuiltin, isJNI, handle); } finally { - NativeLibraries.nativeLibraryContext.pop(); + nativeLibraryContext.pop(); } } } @@ -371,8 +460,8 @@ private static Class getFromClass() { // JNI FindClass expects the caller class if invoked from JNI_OnLoad // and JNI_OnUnload is NativeLibrary class - private static native boolean load(NativeLibraryImpl impl, String name, boolean isBuiltin); - private static native void unload(String name, boolean isBuiltin, long handle); + private static native boolean load(NativeLibraryImpl impl, String name, boolean isBuiltin, boolean isJNI); + private static native void unload(String name, boolean isBuiltin, boolean isJNI, long handle); private static native String findBuiltinLib(String name); private static native long findEntry0(NativeLibraryImpl lib, String name); } diff --git a/src/java.base/share/classes/jdk/internal/loader/NativeLibrary.java b/src/java.base/share/classes/jdk/internal/loader/NativeLibrary.java index 1143f99029c..2b684e0bf35 100644 --- a/src/java.base/share/classes/jdk/internal/loader/NativeLibrary.java +++ b/src/java.base/share/classes/jdk/internal/loader/NativeLibrary.java @@ -31,15 +31,18 @@ public interface NativeLibrary { String name(); - /* + /** * Finds the address of the entry of the given name. Returns 0 * if not found. + * + * @param name the name of the symbol to be found */ long find(String name); - /* + /** * Finds the address of the entry of the given name. * + * @param name the name of the symbol to be found * @throws NoSuchMethodException if the named entry is not found. */ default long lookup(String name) throws NoSuchMethodException { diff --git a/src/java.base/share/native/libjava/NativeLibraries.c b/src/java.base/share/native/libjava/NativeLibraries.c index 6416d7add22..1d973c93a24 100644 --- a/src/java.base/share/native/libjava/NativeLibraries.c +++ b/src/java.base/share/native/libjava/NativeLibraries.c @@ -109,11 +109,11 @@ static void *findJniFunction(JNIEnv *env, void *handle, /* * Class: jdk_internal_loader_NativeLibraries * Method: load - * Signature: (Ljava/lang/String;Z)Z + * Signature: (Ljava/lang/String;ZZ)Z */ JNIEXPORT jboolean JNICALL Java_jdk_internal_loader_NativeLibraries_load - (JNIEnv *env, jobject this, jobject lib, jstring name, jboolean isBuiltin) + (JNIEnv *env, jobject this, jobject lib, jstring name, jboolean isBuiltin, jboolean isJNI) { const char *cname; jint jniVersion; @@ -128,50 +128,52 @@ Java_jdk_internal_loader_NativeLibraries_load if (cname == 0) return JNI_FALSE; handle = isBuiltin ? procHandle : JVM_LoadLibrary(cname); - if (handle) { - JNI_OnLoad_t JNI_OnLoad; - JNI_OnLoad = (JNI_OnLoad_t)findJniFunction(env, handle, - isBuiltin ? cname : NULL, - JNI_TRUE); - if (JNI_OnLoad) { - JavaVM *jvm; - (*env)->GetJavaVM(env, &jvm); - jniVersion = (*JNI_OnLoad)(jvm, NULL); - } else { - jniVersion = 0x00010001; - } + if (isJNI) { + if (handle) { + JNI_OnLoad_t JNI_OnLoad; + JNI_OnLoad = (JNI_OnLoad_t)findJniFunction(env, handle, + isBuiltin ? cname : NULL, + JNI_TRUE); + if (JNI_OnLoad) { + JavaVM *jvm; + (*env)->GetJavaVM(env, &jvm); + jniVersion = (*JNI_OnLoad)(jvm, NULL); + } else { + jniVersion = 0x00010001; + } - cause = (*env)->ExceptionOccurred(env); - if (cause) { - (*env)->ExceptionClear(env); - (*env)->Throw(env, cause); - if (!isBuiltin) { - JVM_UnloadLibrary(handle); + cause = (*env)->ExceptionOccurred(env); + if (cause) { + (*env)->ExceptionClear(env); + (*env)->Throw(env, cause); + if (!isBuiltin) { + JVM_UnloadLibrary(handle); + } + goto done; } - goto done; - } - if (!JVM_IsSupportedJNIVersion(jniVersion) || - (isBuiltin && jniVersion < JNI_VERSION_1_8)) { - char msg[256]; - jio_snprintf(msg, sizeof(msg), - "unsupported JNI version 0x%08X required by %s", - jniVersion, cname); - JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError", msg); - if (!isBuiltin) { - JVM_UnloadLibrary(handle); + if (!JVM_IsSupportedJNIVersion(jniVersion) || + (isBuiltin && jniVersion < JNI_VERSION_1_8)) { + char msg[256]; + jio_snprintf(msg, sizeof(msg), + "unsupported JNI version 0x%08X required by %s", + jniVersion, cname); + JNU_ThrowByName(env, "java/lang/UnsatisfiedLinkError", msg); + if (!isBuiltin) { + JVM_UnloadLibrary(handle); + } + goto done; + } + (*env)->SetIntField(env, lib, jniVersionID, jniVersion); + } else { + cause = (*env)->ExceptionOccurred(env); + if (cause) { + (*env)->ExceptionClear(env); + (*env)->SetLongField(env, lib, handleID, (jlong)0); + (*env)->Throw(env, cause); } goto done; } - (*env)->SetIntField(env, lib, jniVersionID, jniVersion); - } else { - cause = (*env)->ExceptionOccurred(env); - if (cause) { - (*env)->ExceptionClear(env); - (*env)->SetLongField(env, lib, handleID, (jlong)0); - (*env)->Throw(env, cause); - } - goto done; } (*env)->SetLongField(env, lib, handleID, ptr_to_jlong(handle)); loaded = JNI_TRUE; @@ -184,11 +186,11 @@ Java_jdk_internal_loader_NativeLibraries_load /* * Class: jdk_internal_loader_NativeLibraries * Method: unload - * Signature: (Ljava/lang/String;ZJ)V + * Signature: (Ljava/lang/String;ZZJ)V */ JNIEXPORT void JNICALL Java_jdk_internal_loader_NativeLibraries_unload -(JNIEnv *env, jclass cls, jstring name, jboolean isBuiltin, jlong address) +(JNIEnv *env, jclass cls, jstring name, jboolean isBuiltin, jboolean isJNI, jlong address) { const char *onUnloadSymbols[] = JNI_ONUNLOAD_SYMBOLS; void *handle; @@ -202,13 +204,15 @@ Java_jdk_internal_loader_NativeLibraries_unload return; } handle = jlong_to_ptr(address); - JNI_OnUnload = (JNI_OnUnload_t )findJniFunction(env, handle, - isBuiltin ? cname : NULL, - JNI_FALSE); - if (JNI_OnUnload) { - JavaVM *jvm; - (*env)->GetJavaVM(env, &jvm); - (*JNI_OnUnload)(jvm, NULL); + if (isJNI) { + JNI_OnUnload = (JNI_OnUnload_t )findJniFunction(env, handle, + isBuiltin ? cname : NULL, + JNI_FALSE); + if (JNI_OnUnload) { + JavaVM *jvm; + (*env)->GetJavaVM(env, &jvm); + (*JNI_OnUnload)(jvm, NULL); + } } if (!isBuiltin) { JVM_UnloadLibrary(handle); @@ -299,5 +303,3 @@ Java_jdk_internal_loader_NativeLibraries_findBuiltinLib free(libName); return NULL; } - - diff --git a/test/jdk/jdk/internal/loader/NativeLibraries/Main.java b/test/jdk/jdk/internal/loader/NativeLibraries/Main.java new file mode 100644 index 00000000000..6db9100a8e8 --- /dev/null +++ b/test/jdk/jdk/internal/loader/NativeLibraries/Main.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* + * @test + * @bug 8240975 + * @modules java.base/jdk.internal.loader + * @build java.base/* p.Test Main + * @run main/othervm/native -Xcheck:jni Main + * @summary Test loading and unloading of native libraries + */ + +import jdk.internal.loader.*; +import jdk.internal.loader.NativeLibrariesTest; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class Main { + public static void main(String... args) throws Exception { + setup(); + + NativeLibrariesTest test = new NativeLibrariesTest(); + test.runTest(); + + try { + System.loadLibrary(NativeLibrariesTest.LIB_NAME); + } catch (UnsatisfiedLinkError e) { e.printStackTrace(); } + + // unload the native library and then System::loadLibrary should succeed + test.unload(); + System.loadLibrary(NativeLibrariesTest.LIB_NAME); + + // expect NativeLibraries to fail since the library has been loaded by System::loadLibrary + try { + test.load(false); + } catch (UnsatisfiedLinkError e) { e.printStackTrace(); } + } + /* + * move p/Test.class out from classpath to the scratch directory + */ + static void setup() throws IOException { + String dir = System.getProperty("test.classes", "."); + Path p = Files.createDirectories(Paths.get("classes").resolve("p")); + Files.move(Paths.get(dir, "p", "Test.class"), p.resolve("Test.class")); + } + +} diff --git a/test/jdk/jdk/internal/loader/NativeLibraries/java.base/jdk/internal/loader/NativeLibrariesTest.java b/test/jdk/jdk/internal/loader/NativeLibraries/java.base/jdk/internal/loader/NativeLibrariesTest.java new file mode 100644 index 00000000000..1c4ad3dcae7 --- /dev/null +++ b/test/jdk/jdk/internal/loader/NativeLibraries/java.base/jdk/internal/loader/NativeLibrariesTest.java @@ -0,0 +1,135 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +package jdk.internal.loader; + +import java.lang.reflect.Constructor; +import java.net.MalformedURLException; +import java.net.URL; +import java.net.URLClassLoader; +import java.nio.file.Paths; + +public class NativeLibrariesTest implements Runnable { + public static final String LIB_NAME = "nativeLibrariesTest"; + // increments when JNI_OnLoad and JNI_OnUnload is invoked. + // This is only for JNI native library + private static int loadedCount = 0; + private static int unloadedCount = 0; + /* + * Called by JNI_OnLoad when the native library is unloaded + */ + static void nativeLibraryLoaded() { + loadedCount++; + } + + /* + * Called by JNI_OnUnload when the native library is unloaded + */ + static void nativeLibraryUnloaded() { + unloadedCount++; + } + + private final NativeLibraries nativeLibraries; + public NativeLibrariesTest() { + this.nativeLibraries = NativeLibraries.rawNativeLibraries(NativeLibraries.class, true); + } + + /* + * Invoke by p.Test to load the same native library from different class loader + */ + public void run() { + load(true); // expect loading of native library succeed + } + + public void runTest() throws Exception { + NativeLibrary nl1 = nativeLibraries.loadLibrary(LIB_NAME); + NativeLibrary nl2 = nativeLibraries.loadLibrary(LIB_NAME); + assertTrue(nl1 != null && nl2 != null, "fail to load library"); + assertTrue(nl1 == nl2, nl1 + " != " + nl2); + assertTrue(loadedCount == 0, "Native library loaded. Expected: JNI_OnUnload not invoked"); + assertTrue(unloadedCount == 0, "native library never unloaded"); + + // load successfully even from another loader + loadWithCustomLoader(); + + // unload the native library + nativeLibraries.unload(nl1); + assertTrue(unloadedCount == 0, "Native library unloaded. Expected: JNI_OnUnload not invoked"); + + // reload the native library and expect new NativeLibrary instance + NativeLibrary nl3 = nativeLibraries.loadLibrary(LIB_NAME); + assertTrue(nl1 != nl3, nl1 + " == " + nl3); + assertTrue(loadedCount == 0, "Native library loaded. Expected: JNI_OnUnload not invoked"); + + // load successfully even from another loader + loadWithCustomLoader(); + } + + public void unload() { + NativeLibrary nl = nativeLibraries.loadLibrary(LIB_NAME); + // unload the native library + nativeLibraries.unload(nl); + assertTrue(unloadedCount == 0, "Native library unloaded. Expected: JNI_OnUnload not invoked"); + } + + public void load(boolean succeed) { + NativeLibrary nl = nativeLibraries.loadLibrary(LIB_NAME); + if (succeed) { + assertTrue(nl != null, "fail to load library"); + } else { + assertTrue(nl == null, "load library should fail"); + } + } + + /* + * Loads p.Test class with a new class loader and invokes the run() method. + * p.Test::run invokes NativeLibrariesTest::run + */ + private void loadWithCustomLoader() throws Exception { + TestLoader loader = new TestLoader(); + Class c = Class.forName("p.Test", true, loader); + Constructor ctr = c.getConstructor(Runnable.class); + Runnable r = (Runnable) ctr.newInstance(this); + r.run(); + } + + static class TestLoader extends URLClassLoader { + static URL[] toURLs() { + try { + return new URL[] { Paths.get("classes").toUri().toURL() }; + } catch (MalformedURLException e) { + throw new Error(e); + } + } + + TestLoader() { + super("testloader", toURLs(), ClassLoader.getSystemClassLoader()); + } + } + + static void assertTrue(boolean value, String msg) { + if (!value) { + throw new AssertionError(msg); + } + } +} diff --git a/test/jdk/jdk/internal/loader/NativeLibraries/libnativeLibrariesTest.c b/test/jdk/jdk/internal/loader/NativeLibraries/libnativeLibrariesTest.c new file mode 100644 index 00000000000..c4025fc947f --- /dev/null +++ b/test/jdk/jdk/internal/loader/NativeLibraries/libnativeLibrariesTest.c @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include +#include + +#include "jni.h" +#include "jni_util.h" + +static jclass test_class; +static jint current_jni_version = JNI_VERSION_10; + +JNIEXPORT jint JNICALL +JNI_OnLoad(JavaVM *vm, void *reserved) { + JNIEnv *env; + jclass cl; + jmethodID mid; + + (*vm)->GetEnv(vm, (void **) &env, current_jni_version); + + cl = (*env)->FindClass(env, "jdk/internal/loader/NativeLibrariesTest"); + test_class = (*env)->NewGlobalRef(env, cl); + + mid = (*env)->GetStaticMethodID(env, test_class, "nativeLibraryLoaded", "()V"); + (*env)->CallStaticVoidMethod(env, test_class, mid); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->FatalError(env, "Exception thrown"); + } + return current_jni_version; +} + +JNIEXPORT void JNICALL +JNI_OnUnload(JavaVM *vm, void *reserved) { + JNIEnv *env; + jmethodID mid; + + (*vm)->GetEnv(vm, (void **) &env, current_jni_version); + mid = (*env)->GetStaticMethodID(env, test_class, "nativeLibraryUnloaded", "()V"); + (*env)->CallStaticVoidMethod(env, test_class, mid); + if ((*env)->ExceptionCheck(env)) { + (*env)->ExceptionDescribe(env); + (*env)->FatalError(env, "Exception thrown"); + } +} diff --git a/test/jdk/jdk/internal/loader/NativeLibraries/p/Test.java b/test/jdk/jdk/internal/loader/NativeLibraries/p/Test.java new file mode 100644 index 00000000000..c762b63f098 --- /dev/null +++ b/test/jdk/jdk/internal/loader/NativeLibraries/p/Test.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ +package p; + +public class Test implements Runnable { + private final Runnable r; + public Test(Runnable r) { + this.r = r; + } + + /** + * Tests if the native library is loaded. + */ + public void run() { + r.run(); + } +}