/* * Copyright (c) 2020, 2025, 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. Oracle designates this * particular file as subject to the "Classpath" exception as provided * by Oracle in the LICENSE file that accompanied this code. * * 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 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.function.BiFunction; import java.util.function.Function; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.locks.ReentrantLock; /** * Native libraries are loaded via {@link System#loadLibrary(String)}, * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and * {@link Runtime#load(String)}. They are caller-sensitive. * * Each class loader has a NativeLibraries instance to register all of its * loaded native libraries. System::loadLibrary (and other APIs) only * allows a native library to be loaded by one class loader, i.e. one * NativeLibraries instance. Any attempt to load a native library that * has already been loaded by a class loader with another class loader * will fail. */ public final class NativeLibraries { private static final boolean loadLibraryOnlyIfPresent = ClassLoaderHelper.loadLibraryOnlyIfPresent(); private final Map libraries = new ConcurrentHashMap<>(); private final ClassLoader loader; // caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary // unless specified private final Class caller; // may be null private final boolean searchJavaLibraryPath; /** * 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 newInstance(ClassLoader loader) { return new NativeLibraries(loader, loader != null ? null : NativeLibraries.class, loader != null); } private NativeLibraries(ClassLoader loader, Class caller, boolean searchJavaLibraryPath) { this.loader = loader; this.caller = caller; this.searchJavaLibraryPath = searchJavaLibraryPath; } /* * Find the address of the given symbol name from the native libraries * loaded in this NativeLibraries instance. */ public long find(String name) { if (libraries.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 : libraries.values()) { long entry = lib.find(name); if (entry != 0) return entry; } return 0; } /* * Load a native library from the given file. Returns null if the given * library is determined to be non-loadable, which is system-dependent. * * @param fromClass the caller class calling System::loadLibrary * @param file the path of the native library * @throws UnsatisfiedLinkError if any error in loading the native library */ public NativeLibrary loadLibrary(Class fromClass, File file) { // Check to see if we're attempting to access a static library String name = findBuiltinLib(file.getName()); boolean isBuiltin = (name != null); if (!isBuiltin) { try { if (loadLibraryOnlyIfPresent && !file.exists()) { return null; } name = file.getCanonicalPath(); } catch (IOException e) { return null; } } return loadLibrary(fromClass, name, isBuiltin); } /** * Returns a NativeLibrary of the given name. * * @param fromClass the caller class calling System::loadLibrary * @param name library name * @param isBuiltin built-in library * @throws UnsatisfiedLinkError if the native library has already been loaded * and registered in another NativeLibraries */ private NativeLibrary loadLibrary(Class fromClass, String name, boolean isBuiltin) { ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); if (this.loader != loader) { throw new InternalError(fromClass.getName() + " not allowed to load library"); } acquireNativeLibraryLock(name); try { // find if this library has already been loaded and registered in this NativeLibraries NativeLibrary cached = libraries.get(name); if (cached != null) { return cached; } // cannot be loaded by other class loaders if (Holder.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. * * Each thread maintains its own stack to hold the list of * libraries it is loading. * * If there is a pending load operation for the library, we * immediately return success; if the pending load is from * a different class loader, we raise UnsatisfiedLinkError. */ for (NativeLibraryImpl lib : NativeLibraryContext.current()) { if (name.equals(lib.name())) { if (loader == lib.fromClass.getClassLoader()) { return lib; } else { throw new UnsatisfiedLinkError("Native Library " + name + " is being loaded in another classloader"); } } } NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin); // load the native library NativeLibraryContext.push(lib); try { 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 = !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 Holder.loadedLibraryNames.add(name); libraries.put(name, lib); return lib; } finally { releaseNativeLibraryLock(name); } } /** * Loads a native library from the system library path and java library path. * * @param name library name * * @throws UnsatisfiedLinkError if the native library has already been loaded * and registered in another NativeLibraries */ public NativeLibrary loadLibrary(String name) { assert name.indexOf(File.separatorChar) < 0; return loadLibrary(caller, name); } /** * Loads a native library from the system library path and java library path. * * @param name library name * @param fromClass the caller class calling System::loadLibrary * * @throws UnsatisfiedLinkError if the native library has already been loaded * and registered in another NativeLibraries */ public NativeLibrary loadLibrary(Class fromClass, String name) { assert name.indexOf(File.separatorChar) < 0; NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name); if (lib == null && searchJavaLibraryPath) { lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name); } return lib; } // Called at the end of AOTCache assembly phase. public void clear() { libraries.clear(); } 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) { return nl; } libfile = ClassLoaderHelper.mapAlternativeName(libfile); if (libfile != null) { nl = loadLibrary(fromClass, libfile); if (nl != null) { return nl; } } } return null; } /** * NativeLibraryImpl denotes a loaded native library instance. * Each NativeLibraries contains a map of loaded native libraries in the * private field {@code libraries}. * * Every native library requires a particular version of JNI. This is * denoted by the private {@code jniVersion} field. This field is set by * the VM when it loads the library, and used by the VM to pass the correct * version of JNI to the native methods. */ static class NativeLibraryImpl extends 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. int jniVersion; NativeLibraryImpl(Class fromClass, String name, boolean isBuiltin) { this.fromClass = fromClass; this.name = name; this.isBuiltin = isBuiltin; } @Override public String name() { return name; } @Override public long find(String name) { return findEntry0(handle, name); } /* * Unloader::run method is invoked to unload the native library * when this class loader becomes phantom reachable. */ private Runnable unloader() { return new Unloader(name, handle, isBuiltin); } /* * Loads the named native library */ boolean open() { if (handle != 0) { throw new InternalError("Native library " + name + " has been loaded"); } return load(this, name, isBuiltin, throwExceptionIfFail()); } @SuppressWarnings("removal") private boolean throwExceptionIfFail() { if (loadLibraryOnlyIfPresent) return true; // If the file exists but fails to load, UnsatisfiedLinkException thrown by the VM // will include the error message from dlopen to provide diagnostic information return AccessController.doPrivileged(new PrivilegedAction<>() { public Boolean run() { File file = new File(name); return file.exists(); } }); } /* * Close this native library. */ void close() { unload(name, isBuiltin, handle); } } /* * 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 NativeLibraryImpl UNLOADER = new NativeLibraryImpl(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() { acquireNativeLibraryLock(name); try { /* remove the native library name */ if (!Holder.loadedLibraryNames.remove(name)) { throw new IllegalStateException(name + " has already been unloaded"); } NativeLibraryContext.push(UNLOADER); try { unload(name, isBuiltin, handle); } finally { NativeLibraryContext.pop(); } } finally { releaseNativeLibraryLock(name); } } } /* * Holds system and user library paths derived from the * {@code java.library.path} and {@code sun.boot.library.path} system * properties. The system properties are eagerly read at bootstrap, then * lazily parsed on first use to avoid initialization ordering issues. */ static class LibraryPaths { // The paths searched for libraries static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath()); static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath()); } // Holder has the fields that need to be initialized during JVM bootstrap even if // the outer is aot-initialized. static class Holder { // All native libraries we've loaded. private static final Set loadedLibraryNames = ConcurrentHashMap.newKeySet(); } // reentrant lock class that allows exact counting (with external synchronization) @SuppressWarnings("serial") private static final class CountedLock extends ReentrantLock { private int counter = 0; public void increment() { if (counter == Integer.MAX_VALUE) { // prevent overflow throw new Error("Maximum lock count exceeded"); } ++counter; } public void decrement() { --counter; } public int getCounter() { return counter; } } // Maps native library name to the corresponding lock object private static final Map nativeLibraryLockMap = new ConcurrentHashMap<>(); private static void acquireNativeLibraryLock(String libraryName) { nativeLibraryLockMap.compute(libraryName, new BiFunction<>() { public CountedLock apply(String name, CountedLock currentLock) { if (currentLock == null) { currentLock = new CountedLock(); } // safe as compute BiFunction<> is executed atomically currentLock.increment(); return currentLock; } } ).lock(); } private static void releaseNativeLibraryLock(String libraryName) { CountedLock lock = nativeLibraryLockMap.computeIfPresent(libraryName, new BiFunction<>() { public CountedLock apply(String name, CountedLock currentLock) { if (currentLock.getCounter() == 1) { // unlock and release the object if no other threads are queued currentLock.unlock(); // remove the element return null; } else { currentLock.decrement(); return currentLock; } } } ); if (lock != null) { lock.unlock(); } } // native libraries being loaded private static final class NativeLibraryContext { // Maps thread object to the native library context stack, maintained by each thread private static Map> nativeLibraryThreadContext = new ConcurrentHashMap<>(); // returns a context associated with the current thread private static Deque current() { return nativeLibraryThreadContext.computeIfAbsent( Thread.currentThread(), new Function<>() { public Deque apply(Thread t) { return new ArrayDeque<>(8); } }); } private static NativeLibraryImpl peek() { return current().peek(); } private static void push(NativeLibraryImpl lib) { current().push(lib); } private static void pop() { // this does not require synchronization since each // thread has its own context Deque libs = current(); libs.pop(); if (libs.isEmpty()) { // context can be safely removed once empty nativeLibraryThreadContext.remove(Thread.currentThread()); } } private static boolean isEmpty() { Deque context = nativeLibraryThreadContext.get(Thread.currentThread()); return (context == null || context.isEmpty()); } } // Invoked in the VM to determine the context class in JNI_OnLoad // and JNI_OnUnload private static Class getFromClass() { if (NativeLibraryContext.isEmpty()) { // only default library return Object.class; } return NativeLibraryContext.peek().fromClass; } /* * Return true if the given library is successfully loaded. * If the given library cannot be loaded for any reason, * if throwExceptionIfFail is false, then this method returns false; * otherwise, UnsatisfiedLinkError will be thrown. * * 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, boolean throwExceptionIfFail); /* * Unload the named library. JNI_OnUnload, if present, will be invoked * before the native library is unloaded. */ private static native void unload(String name, boolean isBuiltin, long handle); private static native String findBuiltinLib(String name); }