8164512: Replace ClassLoader use of finalizer with phantom reference to unload native library
authormchung
Mon, 06 Nov 2017 17:48:00 -0800
changeset 47707 67aa34b019e1
parent 47706 5d668ad5142f
child 47708 8c0c0aec3ae7
8164512: Replace ClassLoader use of finalizer with phantom reference to unload native library Reviewed-by: alanb, bchristi, kbarrett, dholmes, plevart
make/mapfiles/libjava/mapfile-vers
make/mapfiles/libjava/reorder-sparc
make/mapfiles/libjava/reorder-sparcv9
make/mapfiles/libjava/reorder-x86
make/test/JtregNativeJdk.gmk
src/java.base/share/classes/java/lang/ClassLoader.java
src/java.base/share/classes/java/lang/Runtime.java
src/java.base/share/classes/java/lang/System.java
src/java.base/share/native/libjava/ClassLoader.c
test/jdk/java/lang/ClassLoader/nativeLibrary/NativeLibraryTest.java
test/jdk/java/lang/ClassLoader/nativeLibrary/libnativeLibraryTest.c
test/jdk/java/lang/ClassLoader/nativeLibrary/p/Test.java
--- a/make/mapfiles/libjava/mapfile-vers	Mon Nov 06 17:35:40 2017 -0500
+++ b/make/mapfiles/libjava/mapfile-vers	Mon Nov 06 17:48:00 2017 -0800
@@ -130,8 +130,8 @@
 		Java_java_lang_ClassLoader_defineClass2;
 		Java_java_lang_ClassLoader_findBuiltinLib;
 		Java_java_lang_ClassLoader_findLoadedClass0;
-		Java_java_lang_ClassLoader_00024NativeLibrary_find;
-		Java_java_lang_ClassLoader_00024NativeLibrary_load;
+		Java_java_lang_ClassLoader_00024NativeLibrary_findEntry;
+		Java_java_lang_ClassLoader_00024NativeLibrary_load0;
 		Java_java_lang_ClassLoader_00024NativeLibrary_unload;
 		Java_java_lang_ClassLoader_registerNatives;
 		Java_java_lang_Double_longBitsToDouble;
--- a/make/mapfiles/libjava/reorder-sparc	Mon Nov 06 17:35:40 2017 -0500
+++ b/make/mapfiles/libjava/reorder-sparc	Mon Nov 06 17:48:00 2017 -0800
@@ -48,8 +48,8 @@
 text: .text%Java_java_io_FileInputStream_close0;
 text: .text%Java_java_lang_System_mapLibraryName;
 text: .text%Java_java_io_UnixFileSystem_getBooleanAttributes0;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_find;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load0;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_findEntry;
 text: .text%Java_java_security_AccessController_doPrivileged__Ljava_security_PrivilegedExceptionAction_2;
 text: .text%Java_java_io_UnixFileSystem_list;
 text: .text%JNU_ClassString;
--- a/make/mapfiles/libjava/reorder-sparcv9	Mon Nov 06 17:35:40 2017 -0500
+++ b/make/mapfiles/libjava/reorder-sparcv9	Mon Nov 06 17:48:00 2017 -0800
@@ -57,8 +57,8 @@
 text: .text%Java_java_io_UnixFileSystem_getBooleanAttributes0;
 text: .text%Java_java_security_AccessController_doPrivileged__Ljava_security_PrivilegedExceptionAction_2Ljava_security_AccessControlContext_2;
 text: .text%Java_java_lang_System_mapLibraryName;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_find;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load0;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_findEntry;
 text: .text%Java_java_io_UnixFileSystem_getLength;
 text: .text%Java_java_lang_Object_getClass;
 text: .text%Java_java_lang_ClassLoader_defineClass0;
--- a/make/mapfiles/libjava/reorder-x86	Mon Nov 06 17:35:40 2017 -0500
+++ b/make/mapfiles/libjava/reorder-x86	Mon Nov 06 17:48:00 2017 -0800
@@ -50,8 +50,8 @@
 text: .text%Java_java_security_AccessController_doPrivileged__Ljava_security_PrivilegedExceptionAction_2Ljava_security_AccessControlContext_2;
 text: .text%Java_java_lang_System_mapLibraryName;
 text: .text%cpchars: OUTPUTDIR/System.o;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_find;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load0;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_findEntry;
 text: .text%Java_java_lang_Float_floatToRawIntBits;
 text: .text%Java_java_lang_Double_doubleToRawLongBits;
 text: .text%Java_java_io_FileInputStream_open0;
--- a/make/test/JtregNativeJdk.gmk	Mon Nov 06 17:35:40 2017 -0500
+++ b/make/test/JtregNativeJdk.gmk	Mon Nov 06 17:48:00 2017 -0800
@@ -44,6 +44,7 @@
 # Add more directories here when needed.
 BUILD_JDK_JTREG_NATIVE_SRC += \
     $(TOPDIR)/test/jdk/native_sanity \
+    $(TOPDIR)/test/jdk/java/lang/ClassLoader/nativeLibrary \
     $(TOPDIR)/test/jdk/java/lang/String/nativeEncoding \
     #
 
--- a/src/java.base/share/classes/java/lang/ClassLoader.java	Mon Nov 06 17:35:40 2017 -0500
+++ b/src/java.base/share/classes/java/lang/ClassLoader.java	Mon Nov 06 17:48:00 2017 -0800
@@ -37,17 +37,20 @@
 import java.security.PrivilegedAction;
 import java.security.ProtectionDomain;
 import java.security.cert.Certificate;
+import java.util.Arrays;
 import java.util.Collections;
+import java.util.Deque;
 import java.util.Enumeration;
 import java.util.HashMap;
+import java.util.HashSet;
 import java.util.Hashtable;
+import java.util.LinkedList;
 import java.util.Map;
 import java.util.NoSuchElementException;
 import java.util.Objects;
 import java.util.Set;
 import java.util.Spliterator;
 import java.util.Spliterators;
-import java.util.Stack;
 import java.util.Vector;
 import java.util.WeakHashMap;
 import java.util.concurrent.ConcurrentHashMap;
@@ -58,9 +61,9 @@
 import jdk.internal.perf.PerfCounter;
 import jdk.internal.loader.BootLoader;
 import jdk.internal.loader.ClassLoaders;
-import jdk.internal.misc.SharedSecrets;
 import jdk.internal.misc.Unsafe;
 import jdk.internal.misc.VM;
+import jdk.internal.ref.CleanerFactory;
 import jdk.internal.reflect.CallerSensitive;
 import jdk.internal.reflect.Reflection;
 import sun.reflect.misc.ReflectUtil;
@@ -2375,75 +2378,161 @@
      * @since    1.2
      */
     static class NativeLibrary {
+        // the class from which the library is loaded, also indicates
+        // the loader this native library belongs.
+        final Class<?> fromClass;
+        // the canonicalized name of the native library.
+        // or static library name
+        final String name;
+        // Indicates if the native library is linked into the VM
+        final boolean isBuiltin;
+
         // opaque handle to native library, used in native code.
         long handle;
         // the version of JNI environment the native library requires.
-        private int jniVersion;
-        // the class from which the library is loaded, also indicates
-        // the loader this native library belongs.
-        private Class<?> fromClass;
-        // the canonicalized name of the native library.
-        // or static library name
-        String name;
-        // Indicates if the native library is linked into the VM
-        boolean isBuiltin;
-        // Indicates if the native library is loaded
-        boolean loaded;
-        native void load(String name, boolean isBuiltin);
+        int jniVersion;
+
+        native boolean load0(String name, boolean isBuiltin);
 
-        native long find(String name);
-        native void unload(String name, boolean isBuiltin);
+        native long findEntry(String name);
 
-        public NativeLibrary(Class<?> fromClass, String name, boolean isBuiltin) {
+        NativeLibrary(Class<?> fromClass, String name, boolean isBuiltin) {
             this.name = name;
             this.fromClass = fromClass;
             this.isBuiltin = isBuiltin;
         }
 
-        @SuppressWarnings("deprecation")
-        protected void finalize() {
-            synchronized (loadedLibraryNames) {
-                if (fromClass.getClassLoader() != null && loaded) {
-                    this.fromClass = null;   // no context when unloaded
+        /*
+         * Loads the native library and registers for cleanup when its
+         * associated class loader is unloaded
+         */
+        boolean load() {
+            if (handle != 0) {
+                throw new InternalError("Native library " + name + " has been loaded");
+            }
+
+            if (!load0(name, isBuiltin)) return false;
+
+            // register the class loader for cleanup when unloaded
+            // built class loaders are never unloaded
+            ClassLoader loader = fromClass.getClassLoader();
+            if (loader != null &&
+                loader != getBuiltinPlatformClassLoader() &&
+                loader != getBuiltinAppClassLoader()) {
+                CleanerFactory.cleaner().register(loader,
+                        new Unloader(name, handle, isBuiltin));
+            }
+            return true;
+        }
+
+        static boolean loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) {
+            ClassLoader loader =
+                fromClass == null ? null : fromClass.getClassLoader();
 
-                    /* remove the native library name */
-                    int size = loadedLibraryNames.size();
-                    for (int i = 0; i < size; i++) {
-                        if (name.equals(loadedLibraryNames.elementAt(i))) {
-                            loadedLibraryNames.removeElementAt(i);
-                            break;
+            synchronized (loadedLibraryNames) {
+                Map<String, NativeLibrary> libs =
+                    loader != null ? loader.nativeLibraries() : systemNativeLibraries();
+                if (libs.containsKey(name)) {
+                    return true;
+                }
+
+                if (loadedLibraryNames.contains(name)) {
+                    throw new UnsatisfiedLinkError("Native Library " + name +
+                        " already loaded in another classloader");
+                }
+
+                /*
+                 * When a library is being loaded, JNI_OnLoad function can cause
+                 * another loadLibrary invocation that should succeed.
+                 *
+                 * We use a static stack to hold the list of libraries we are
+                 * loading because this can happen only when called by the
+                 * same thread because Runtime.load and Runtime.loadLibrary
+                 * are synchronous.
+                 *
+                 * If there is a pending load operation for the library, we
+                 * immediately return success; otherwise, we raise
+                 * UnsatisfiedLinkError.
+                 */
+                for (NativeLibrary lib : nativeLibraryContext) {
+                    if (name.equals(lib.name)) {
+                        if (loader == lib.fromClass.getClassLoader()) {
+                            return true;
+                        } else {
+                            throw new UnsatisfiedLinkError("Native Library " +
+                                name + " is being loaded in another classloader");
                         }
                     }
-                    /* unload the library. */
-                    ClassLoader.nativeLibraryContext.push(this);
+                }
+                NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
+                // load the native library
+                nativeLibraryContext.push(lib);
+                try {
+                    if (!lib.load()) return false;
+                } finally {
+                    nativeLibraryContext.pop();
+                }
+                // register the loaded native library
+                loadedLibraryNames.add(name);
+                libs.put(name, lib);
+            }
+            return true;
+        }
+
+        // Invoked in the VM to determine the context class in JNI_OnLoad
+        // and JNI_OnUnload
+        static Class<?> getFromClass() {
+            return nativeLibraryContext.peek().fromClass;
+        }
+
+        // native libraries being loaded
+        static Deque<NativeLibrary> nativeLibraryContext = new LinkedList<>();
+
+        /*
+         * The run() method will be invoked when this class loader becomes
+         * phantom reachable to unload the native library.
+         */
+        static class Unloader implements Runnable {
+            // This represents the context when a native library is unloaded
+            // and getFromClass() will return null,
+            static final NativeLibrary UNLOADER =
+                new NativeLibrary(null, "dummy", false);
+            final String name;
+            final long handle;
+            final boolean isBuiltin;
+
+            Unloader(String name, long handle, boolean isBuiltin) {
+                if (handle == 0) {
+                    throw new IllegalArgumentException(
+                        "Invalid handle for native library " + name);
+                }
+
+                this.name = name;
+                this.handle = handle;
+                this.isBuiltin = isBuiltin;
+            }
+
+            @Override
+            public void run() {
+                synchronized (loadedLibraryNames) {
+                    /* remove the native library name */
+                    loadedLibraryNames.remove(name);
+                    nativeLibraryContext.push(UNLOADER);
                     try {
-                        unload(name, isBuiltin);
+                        unload(name, isBuiltin, handle);
                     } finally {
-                        ClassLoader.nativeLibraryContext.pop();
+                        nativeLibraryContext.pop();
                     }
+
                 }
             }
         }
-        // Invoked in the VM to determine the context class in
-        // JNI_Load/JNI_Unload
-        static Class<?> getFromClass() {
-            return ClassLoader.nativeLibraryContext.peek().fromClass;
-        }
+
+        // JNI FindClass expects the caller class if invoked from JNI_OnLoad
+        // and JNI_OnUnload is NativeLibrary class
+        static native void unload(String name, boolean isBuiltin, long handle);
     }
 
-    // All native library names we've loaded.
-    private static Vector<String> loadedLibraryNames = new Vector<>();
-
-    // Native libraries belonging to system classes.
-    private static Vector<NativeLibrary> systemNativeLibraries
-        = new Vector<>();
-
-    // Native libraries associated with the class loader.
-    private Vector<NativeLibrary> nativeLibraries = new Vector<>();
-
-    // native libraries being loaded/unloaded.
-    private static Stack<NativeLibrary> nativeLibraryContext = new Stack<>();
-
     // The paths searched for libraries
     private static String usr_paths[];
     private static String sys_paths[];
@@ -2455,7 +2544,7 @@
         int psCount = 0;
 
         if (ClassLoaderHelper.allowsQuotedPathElements &&
-                ldPath.indexOf('\"') >= 0) {
+            ldPath.indexOf('\"') >= 0) {
             // First, remove quotes put around quoted parts of paths.
             // Second, use a quotation mark as a new path separator.
             // This will preserve any quoted old path separators.
@@ -2465,7 +2554,7 @@
                 char ch = ldPath.charAt(i);
                 if (ch == '\"') {
                     while (++i < ldLen &&
-                            (ch = ldPath.charAt(i)) != '\"') {
+                        (ch = ldPath.charAt(i)) != '\"') {
                         buf[bufLen++] = ch;
                     }
                 } else {
@@ -2481,7 +2570,7 @@
             ps = '\"';
         } else {
             for (int i = ldPath.indexOf(ps); i >= 0;
-                    i = ldPath.indexOf(ps, i + 1)) {
+                 i = ldPath.indexOf(ps, i + 1)) {
                 psCount++;
             }
         }
@@ -2491,11 +2580,11 @@
         for (int j = 0; j < psCount; ++j) {
             int pathEnd = ldPath.indexOf(ps, pathStart);
             paths[j] = (pathStart < pathEnd) ?
-                    ldPath.substring(pathStart, pathEnd) : ".";
+                ldPath.substring(pathStart, pathEnd) : ".";
             pathStart = pathEnd + 1;
         }
         paths[psCount] = (pathStart < ldLen) ?
-                ldPath.substring(pathStart, ldLen) : ".";
+            ldPath.substring(pathStart, ldLen) : ".";
         return paths;
     }
 
@@ -2520,7 +2609,7 @@
                 File libfile = new File(libfilename);
                 if (!libfile.isAbsolute()) {
                     throw new UnsatisfiedLinkError(
-    "ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
+                        "ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
                 }
                 if (loadLibrary0(fromClass, libfile)) {
                     return;
@@ -2551,10 +2640,11 @@
             }
         }
         // Oops, it failed
-        throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
+        throw new UnsatisfiedLinkError("no " + name +
+            " in java.library.path: " + Arrays.toString(usr_paths));
     }
 
-    static native String findBuiltinLib(String name);
+    private static native String findBuiltinLib(String name);
 
     private static boolean loadLibrary0(Class<?> fromClass, final File file) {
         // Check to see if we're attempting to access a static library
@@ -2575,85 +2665,72 @@
                 return false;
             }
         }
-        ClassLoader loader =
-            (fromClass == null) ? null : fromClass.getClassLoader();
-        Vector<NativeLibrary> libs =
-            loader != null ? loader.nativeLibraries : systemNativeLibraries;
-        synchronized (libs) {
-            int size = libs.size();
-            for (int i = 0; i < size; i++) {
-                NativeLibrary lib = libs.elementAt(i);
-                if (name.equals(lib.name)) {
-                    return true;
-                }
-            }
-
-            synchronized (loadedLibraryNames) {
-                if (loadedLibraryNames.contains(name)) {
-                    throw new UnsatisfiedLinkError
-                        ("Native Library " +
-                         name +
-                         " already loaded in another classloader");
-                }
-                /* If the library is being loaded (must be by the same thread,
-                 * because Runtime.load and Runtime.loadLibrary are
-                 * synchronous). The reason is can occur is that the JNI_OnLoad
-                 * function can cause another loadLibrary invocation.
-                 *
-                 * Thus we can use a static stack to hold the list of libraries
-                 * we are loading.
-                 *
-                 * If there is a pending load operation for the library, we
-                 * immediately return success; otherwise, we raise
-                 * UnsatisfiedLinkError.
-                 */
-                int n = nativeLibraryContext.size();
-                for (int i = 0; i < n; i++) {
-                    NativeLibrary lib = nativeLibraryContext.elementAt(i);
-                    if (name.equals(lib.name)) {
-                        if (loader == lib.fromClass.getClassLoader()) {
-                            return true;
-                        } else {
-                            throw new UnsatisfiedLinkError
-                                ("Native Library " +
-                                 name +
-                                 " is being loaded in another classloader");
-                        }
-                    }
-                }
-                NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
-                nativeLibraryContext.push(lib);
-                try {
-                    lib.load(name, isBuiltin);
-                } finally {
-                    nativeLibraryContext.pop();
-                }
-                if (lib.loaded) {
-                    loadedLibraryNames.addElement(name);
-                    libs.addElement(lib);
-                    return true;
-                }
-                return false;
-            }
-        }
+        return NativeLibrary.loadLibrary(fromClass, name, isBuiltin);
     }
 
-    // Invoked in the VM class linking code.
-    static long findNative(ClassLoader loader, String name) {
-        Vector<NativeLibrary> libs =
-            loader != null ? loader.nativeLibraries : systemNativeLibraries;
-        synchronized (libs) {
-            int size = libs.size();
-            for (int i = 0; i < size; i++) {
-                NativeLibrary lib = libs.elementAt(i);
-                long entry = lib.find(name);
-                if (entry != 0)
-                    return entry;
-            }
+    /*
+     * Invoked in the VM class linking code.
+     */
+    private static long findNative(ClassLoader loader, String entryName) {
+        Map<String, NativeLibrary> libs =
+            loader != null ? loader.nativeLibraries() : systemNativeLibraries();
+        if (libs.isEmpty())
+            return 0;
+
+        // the native libraries map may be updated in another thread
+        // when a native library is being loaded.  No symbol will be
+        // searched from it yet.
+        for (NativeLibrary lib : libs.values()) {
+            long entry = lib.findEntry(entryName);
+            if (entry != 0) return entry;
         }
         return 0;
     }
 
+    // All native library names we've loaded.
+    // This also serves as the lock to obtain nativeLibraries
+    // and write to nativeLibraryContext.
+    private static final Set<String> loadedLibraryNames = new HashSet<>();
+
+    // Native libraries belonging to system classes.
+    private static volatile Map<String, NativeLibrary> systemNativeLibraries;
+
+    // Native libraries associated with the class loader.
+    private volatile Map<String, NativeLibrary> nativeLibraries;
+
+    /*
+     * Returns the native libraries map associated with bootstrap class loader
+     * This method will create the map at the first time when called.
+     */
+    private static Map<String, NativeLibrary> systemNativeLibraries() {
+        Map<String, NativeLibrary> libs = systemNativeLibraries;
+        if (libs == null) {
+            synchronized (loadedLibraryNames) {
+                libs = systemNativeLibraries;
+                if (libs == null) {
+                    libs = systemNativeLibraries = new ConcurrentHashMap<>();
+                }
+            }
+        }
+        return libs;
+    }
+
+    /*
+     * Returns the native libraries map associated with this class loader
+     * This method will create the map at the first time when called.
+     */
+    private Map<String, NativeLibrary> nativeLibraries() {
+        Map<String, NativeLibrary> libs = nativeLibraries;
+        if (libs == null) {
+            synchronized (loadedLibraryNames) {
+                libs = nativeLibraries;
+                if (libs == null) {
+                    libs = nativeLibraries = new ConcurrentHashMap<>();
+                }
+            }
+        }
+        return libs;
+    }
 
     // -- Assertion management --
 
--- a/src/java.base/share/classes/java/lang/Runtime.java	Mon Nov 06 17:35:40 2017 -0500
+++ b/src/java.base/share/classes/java/lang/Runtime.java	Mon Nov 06 17:48:00 2017 -0800
@@ -765,7 +765,9 @@
      * with the VM, then the JNI_OnLoad_L function exported by the library
      * is invoked rather than attempting to load a dynamic library.
      * A filename matching the argument does not have to exist in the file
-     * system. See the JNI Specification for more details.
+     * system.
+     * See the <a href="{@docRoot}/../specs/jni/index.html"> JNI Specification</a>
+     * for more details.
      *
      * Otherwise, the filename argument is mapped to a native library image in
      * an implementation-dependent manner.
@@ -818,7 +820,8 @@
      * specific prefix, file extension or path. If a native library
      * called {@code libname} is statically linked with the VM, then the
      * JNI_OnLoad_{@code libname} function exported by the library is invoked.
-     * See the JNI Specification for more details.
+     * See the <a href="{@docRoot}/../specs/jni/index.html"> JNI Specification</a>
+     * for more details.
      *
      * Otherwise, the libname argument is loaded from a system library
      * location and mapped to a native library image in an implementation-
--- a/src/java.base/share/classes/java/lang/System.java	Mon Nov 06 17:35:40 2017 -0500
+++ b/src/java.base/share/classes/java/lang/System.java	Mon Nov 06 17:48:00 2017 -0800
@@ -1799,7 +1799,8 @@
      * is invoked rather than attempting to load a dynamic library.
      * A filename matching the argument does not have to exist in the
      * file system.
-     * See the JNI Specification for more details.
+     * See the <a href="{@docRoot}/../specs/jni/index.html"> JNI Specification</a>
+     * for more details.
      *
      * Otherwise, the filename argument is mapped to a native library image in
      * an implementation-dependent manner.
@@ -1835,7 +1836,8 @@
      * specific prefix, file extension or path. If a native library
      * called <code>libname</code> is statically linked with the VM, then the
      * JNI_OnLoad_<code>libname</code> function exported by the library is invoked.
-     * See the JNI Specification for more details.
+     * See the <a href="{@docRoot}/../specs/jni/index.html"> JNI Specification</a>
+     * for more details.
      *
      * Otherwise, the libname argument is loaded from a system library
      * location and mapped to a native library image in an implementation-
--- a/src/java.base/share/native/libjava/ClassLoader.c	Mon Nov 06 17:35:40 2017 -0500
+++ b/src/java.base/share/native/libjava/ClassLoader.c	Mon Nov 06 17:48:00 2017 -0800
@@ -260,7 +260,6 @@
 
 static jfieldID handleID;
 static jfieldID jniVersionID;
-static jfieldID loadedID;
 static void *procHandle;
 
 static jboolean initIDs(JNIEnv *env)
@@ -276,9 +275,6 @@
         jniVersionID = (*env)->GetFieldID(env, this, "jniVersion", "I");
         if (jniVersionID == 0)
             return JNI_FALSE;
-        loadedID = (*env)->GetFieldID(env, this, "loaded", "Z");
-        if (loadedID == 0)
-             return JNI_FALSE;
         procHandle = getProcessHandle();
     }
     return JNI_TRUE;
@@ -335,30 +331,31 @@
 
 /*
  * Class:     java_lang_ClassLoader_NativeLibrary
- * Method:    load
- * Signature: (Ljava/lang/String;Z)V
+ * Method:    load0
+ * Signature: (Ljava/lang/String;Z)Z
  */
-JNIEXPORT void JNICALL
-Java_java_lang_ClassLoader_00024NativeLibrary_load
+JNIEXPORT jboolean JNICALL
+Java_java_lang_ClassLoader_00024NativeLibrary_load0
   (JNIEnv *env, jobject this, jstring name, jboolean isBuiltin)
 {
     const char *cname;
     jint jniVersion;
     jthrowable cause;
     void * handle;
+    jboolean loaded = JNI_FALSE;
 
     if (!initIDs(env))
-        return;
+        return JNI_FALSE;
 
     cname = JNU_GetStringPlatformChars(env, name, 0);
     if (cname == 0)
-        return;
+        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);
+                                                   isBuiltin ? cname : NULL,
+                                                   JNI_TRUE);
         if (JNI_OnLoad) {
             JavaVM *jvm;
             (*env)->GetJavaVM(env, &jvm);
@@ -400,20 +397,21 @@
         goto done;
     }
     (*env)->SetLongField(env, this, handleID, ptr_to_jlong(handle));
-    (*env)->SetBooleanField(env, this, loadedID, JNI_TRUE);
+    loaded = JNI_TRUE;
 
  done:
     JNU_ReleaseStringPlatformChars(env, name, cname);
+    return loaded;
 }
 
 /*
  * Class:     java_lang_ClassLoader_NativeLibrary
  * Method:    unload
- * Signature: (Z)V
+ * Signature: (Ljava/lang/String;ZJ)V
  */
 JNIEXPORT void JNICALL
 Java_java_lang_ClassLoader_00024NativeLibrary_unload
-(JNIEnv *env, jobject this, jstring name, jboolean isBuiltin)
+(JNIEnv *env, jclass cls, jstring name, jboolean isBuiltin, jlong address)
 {
     const char *onUnloadSymbols[] = JNI_ONUNLOAD_SYMBOLS;
     void *handle;
@@ -426,10 +424,10 @@
     if (cname == NULL) {
         return;
     }
-    handle = jlong_to_ptr((*env)->GetLongField(env, this, handleID));
+    handle = jlong_to_ptr(address);
     JNI_OnUnload = (JNI_OnUnload_t )findJniFunction(env, handle,
-                                                isBuiltin ? cname : NULL,
-                                                JNI_FALSE);
+                                                    isBuiltin ? cname : NULL,
+                                                    JNI_FALSE);
     if (JNI_OnUnload) {
         JavaVM *jvm;
         (*env)->GetJavaVM(env, &jvm);
@@ -443,11 +441,11 @@
 
 /*
  * Class:     java_lang_ClassLoader_NativeLibrary
- * Method:    find
+ * Method:    findEntry
  * Signature: (Ljava/lang/String;)J
  */
 JNIEXPORT jlong JNICALL
-Java_java_lang_ClassLoader_00024NativeLibrary_find
+Java_java_lang_ClassLoader_00024NativeLibrary_findEntry
   (JNIEnv *env, jobject this, jstring name)
 {
     jlong handle;
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/ClassLoader/nativeLibrary/NativeLibraryTest.java	Mon Nov 06 17:48:00 2017 -0800
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2017, 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 8164512
+ * @summary verify if the native library is unloaded when the class loader is GC'ed
+ * @build p.Test
+ * @run main/othervm/native -Xcheck:jni NativeLibraryTest
+ */
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class NativeLibraryTest {
+    static final Path CLASSES = Paths.get("classes");
+    static int unloadedCount = 0;
+
+    /*
+     * Called by JNI_OnUnload when the native library is unloaded
+     */
+    static void nativeLibraryUnloaded() {
+        unloadedCount++;
+    }
+
+    public static void main(String... args) throws Exception {
+        setup();
+
+        for (int count=1; count <= 5; count++) {
+            // create a class loader and load a native library
+            runTest();
+            // unloading the class loader and native library
+            System.gc();
+            // give Cleaner thread a chance to unload the native library
+            Thread.sleep(100);
+
+            // unloadedCount is incremented when the native library is unloaded
+            if (count != unloadedCount) {
+                throw new RuntimeException("Expected unloaded=" + count +
+                    " but got=" + unloadedCount);
+            }
+        }
+    }
+
+    /*
+     * Loads p.Test class with a new class loader and its static initializer
+     * will load a native library.
+     *
+     * The class loader becomes unreachable when this method returns and
+     * the native library should be unloaded at some point after the class
+     * loader is garbage collected.
+     */
+    static void runTest() throws Exception {
+        // invoke p.Test.run() that loads the native library
+        Runnable r = newTestRunnable();
+        r.run();
+
+        // reload the native library by the same class loader
+        r.run();
+
+        // load the native library by another class loader
+        Runnable r1 = newTestRunnable();
+        try {
+            r1.run();
+            throw new RuntimeException("should fail to load the native library" +
+                    " by another class loader");
+        } catch (UnsatisfiedLinkError e) {}
+    }
+
+    /*
+     * Loads p.Test class with a new class loader and returns
+     * a Runnable instance.
+     */
+    static Runnable newTestRunnable() throws Exception {
+        TestLoader loader = new TestLoader();
+        Class<?> c = Class.forName("p.Test", true, loader);
+        return (Runnable) c.newInstance();
+    }
+
+    static class TestLoader extends URLClassLoader {
+        static URL[] toURLs() {
+            try {
+                return new URL[] { CLASSES.toUri().toURL() };
+            } catch (MalformedURLException e) {
+                throw new Error(e);
+            }
+        }
+
+        TestLoader() {
+            super("testloader", toURLs(), ClassLoader.getSystemClassLoader());
+        }
+    }
+
+    /*
+     * move p/Test.class out from classpath to the scratch directory
+     */
+    static void setup() throws IOException {
+        String dir = System.getProperty("test.classes", ".");
+        Path file = Paths.get("p", "Test.class");
+        Files.createDirectories(CLASSES.resolve("p"));
+        Files.move(Paths.get(dir).resolve(file),
+                   CLASSES.resolve("p").resolve("Test.class"));
+    }
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/ClassLoader/nativeLibrary/libnativeLibraryTest.c	Mon Nov 06 17:48:00 2017 -0800
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2017, 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 <stdio.h>
+#include <stdlib.h>
+
+#include "jni.h"
+#include "jni_util.h"
+
+static jint count = 0;
+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;
+
+    (*vm)->GetEnv(vm, (void **) &env, current_jni_version);
+
+    cl = (*env)->FindClass(env, "NativeLibraryTest");
+    test_class = (*env)->NewGlobalRef(env, cl);
+
+    // increment the count when JNI_OnLoad is called
+    count++;
+
+    return current_jni_version;
+}
+
+JNIEXPORT void JNICALL
+JNI_OnUnload(JavaVM *vm, void *reserved) {
+    JNIEnv *env;
+    jmethodID mid;
+    jclass cl;
+
+    (*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");
+    }
+
+    cl = (*env)->FindClass(env, "p/Test");
+    if (cl != NULL) {
+        (*env)->FatalError(env, "p/Test class should not be found");
+    }
+}
+
+JNIEXPORT jint JNICALL
+Java_p_Test_count
+(JNIEnv *env, jclass cls) {
+    return count;
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/ClassLoader/nativeLibrary/p/Test.java	Mon Nov 06 17:48:00 2017 -0800
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2017, 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 {
+    public static native int count();
+
+    /**
+     * Tests if the native library is loaded.
+     */
+    public void run() {
+        System.loadLibrary("nativeLibraryTest");
+        if (count() != 1) {
+            throw new RuntimeException("Expected count = 1 but got " + count());
+        }
+    }
+}