< prev index next >

src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java

Print this page

        

@@ -22,20 +22,22 @@
  * or visit www.oracle.com if you need additional information or have any
  * questions.
  */
 package jdk.internal.loader;
 
+import jdk.internal.misc.VM;
 import jdk.internal.ref.CleanerFactory;
 import jdk.internal.util.StaticProperty;
 
 import java.io.File;
 import java.io.IOException;
 import java.security.AccessController;
 import java.security.PrivilegedAction;
 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;
 
 /**

@@ -50,28 +52,77 @@
  * has already been loaded by a class loader with another class loader
  * will fail.
  */
 public final class NativeLibraries {
 
-    private final Map<String, NativeLibrary> libraries = new ConcurrentHashMap<>();
+    private final Map<String, NativeLibraryImpl> 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 <a href="${docroot}/specs/jni/invocation.html##library-and-version-management">
+     *     JNI Specification: Library and Version Management</a>
+     */
+    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;
     }
 
     /*
      * Find the address of the given symbol name from the native libraries
      * loaded in this NativeLibraries instance.

@@ -167,15 +218,30 @@
                                 name + " is being loaded in another classloader");
                     }
                 }
             }
 
-            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();
             }
             // register the loaded native library
             loadedLibraryNames.add(name);

@@ -216,10 +282,30 @@
             lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, 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));
             NativeLibrary nl = loadLibrary(fromClass, libfile);
             if (nl != null) {

@@ -253,20 +339,25 @@
         // 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;
+        // 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
         public String name() {
             return name;

@@ -275,30 +366,23 @@
         @Override
         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);
         }
     }
 
     /*
      * The run() method will be invoked when this class loader becomes

@@ -306,37 +390,42 @@
      */
     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);
             }
 
             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();
                 }
             }
         }
     }
 

@@ -369,10 +458,10 @@
         return nativeLibraryContext.peek().fromClass;
     }
 
     // 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);
 }
< prev index next >