Path: blob/master/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java
41159 views
/*1* Copyright (c) 2020, 2021, Oracle and/or its affiliates. All rights reserved.2* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.3*4* This code is free software; you can redistribute it and/or modify it5* under the terms of the GNU General Public License version 2 only, as6* published by the Free Software Foundation. Oracle designates this7* particular file as subject to the "Classpath" exception as provided8* by Oracle in the LICENSE file that accompanied this code.9*10* This code is distributed in the hope that it will be useful, but WITHOUT11* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or12* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License13* version 2 for more details (a copy is included in the LICENSE file that14* accompanied this code).15*16* You should have received a copy of the GNU General Public License version17* 2 along with this work; if not, write to the Free Software Foundation,18* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.19*20* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA21* or visit www.oracle.com if you need additional information or have any22* questions.23*/24package jdk.internal.loader;2526import jdk.internal.misc.VM;27import jdk.internal.ref.CleanerFactory;28import jdk.internal.util.StaticProperty;2930import java.io.File;31import java.io.IOException;32import java.security.AccessController;33import java.security.PrivilegedAction;34import java.util.ArrayDeque;35import java.util.Deque;36import java.util.HashSet;37import java.util.Objects;38import java.util.Map;39import java.util.Set;40import java.util.concurrent.ConcurrentHashMap;4142/**43* Native libraries are loaded via {@link System#loadLibrary(String)},44* {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and45* {@link Runtime#load(String)}. They are caller-sensitive.46*47* Each class loader has a NativeLibraries instance to register all of its48* loaded native libraries. System::loadLibrary (and other APIs) only49* allows a native library to be loaded by one class loader, i.e. one50* NativeLibraries instance. Any attempt to load a native library that51* has already been loaded by a class loader with another class loader52* will fail.53*/54public final class NativeLibraries {5556private final Map<String, NativeLibraryImpl> libraries = new ConcurrentHashMap<>();57private final ClassLoader loader;58// caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary59// unless specified60private final Class<?> caller; // may be null61private final boolean searchJavaLibraryPath;62// loading JNI native libraries63private final boolean isJNI;6465/**66* Creates a NativeLibraries instance for loading JNI native libraries67* via for System::loadLibrary use.68*69* 1. Support of auto-unloading. The loaded native libraries are unloaded70* when the class loader is reclaimed.71* 2. Support of linking of native method. See JNI spec.72* 3. Restriction on a native library that can only be loaded by one class loader.73* Each class loader manages its own set of native libraries.74* The same JNI native library cannot be loaded into more than one class loader.75*76* This static factory method is intended only for System::loadLibrary use.77*78* @see <a href="${docroot}/specs/jni/invocation.html##library-and-version-management">79* JNI Specification: Library and Version Management</a>80*/81public static NativeLibraries jniNativeLibraries(ClassLoader loader) {82return new NativeLibraries(loader);83}8485/**86* Creates a raw NativeLibraries instance that has the following properties:87* 1. Native libraries loaded in this raw NativeLibraries instance are88* not JNI native libraries. Hence JNI_OnLoad and JNI_OnUnload will89* be ignored. No support for linking of native method.90* 2. Native libraries not auto-unloaded. They may be explicitly unloaded91* via NativeLibraries::unload.92* 3. No relationship with class loaders.93*94* This static factory method is restricted for JDK trusted class use.95*/96public static NativeLibraries rawNativeLibraries(Class<?> trustedCaller,97boolean searchJavaLibraryPath) {98return new NativeLibraries(trustedCaller, searchJavaLibraryPath);99}100101private NativeLibraries(ClassLoader loader) {102// for null loader, default the caller to this class and103// do not search java.library.path104this.loader = loader;105this.caller = loader != null ? null : NativeLibraries.class;106this.searchJavaLibraryPath = loader != null ? true : false;107this.isJNI = true;108}109110/*111* Constructs a NativeLibraries instance of no relationship with class loaders112* and disabled auto unloading.113*/114private NativeLibraries(Class<?> caller, boolean searchJavaLibraryPath) {115Objects.requireNonNull(caller);116if (!VM.isSystemDomainLoader(caller.getClassLoader())) {117throw new IllegalArgumentException("must be JDK trusted class");118}119this.loader = caller.getClassLoader();120this.caller = caller;121this.searchJavaLibraryPath = searchJavaLibraryPath;122this.isJNI = false;123}124125/*126* Find the address of the given symbol name from the native libraries127* loaded in this NativeLibraries instance.128*/129public long find(String name) {130if (libraries.isEmpty())131return 0;132133// the native libraries map may be updated in another thread134// when a native library is being loaded. No symbol will be135// searched from it yet.136for (NativeLibrary lib : libraries.values()) {137long entry = lib.find(name);138if (entry != 0) return entry;139}140return 0;141}142143/*144* Load a native library from the given file. Returns null if file does not exist.145*146* @param fromClass the caller class calling System::loadLibrary147* @param file the path of the native library148* @throws UnsatisfiedLinkError if any error in loading the native library149*/150@SuppressWarnings("removal")151public NativeLibrary loadLibrary(Class<?> fromClass, File file) {152// Check to see if we're attempting to access a static library153String name = findBuiltinLib(file.getName());154boolean isBuiltin = (name != null);155if (!isBuiltin) {156name = AccessController.doPrivileged(new PrivilegedAction<>() {157public String run() {158try {159return file.exists() ? file.getCanonicalPath() : null;160} catch (IOException e) {161return null;162}163}164});165if (name == null) {166return null;167}168}169return loadLibrary(fromClass, name, isBuiltin);170}171172/**173* Returns a NativeLibrary of the given name.174*175* @param fromClass the caller class calling System::loadLibrary176* @param name library name177* @param isBuiltin built-in library178* @throws UnsatisfiedLinkError if the native library has already been loaded179* and registered in another NativeLibraries180*/181private NativeLibrary loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) {182ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader();183if (this.loader != loader) {184throw new InternalError(fromClass.getName() + " not allowed to load library");185}186187synchronized (loadedLibraryNames) {188// find if this library has already been loaded and registered in this NativeLibraries189NativeLibrary cached = libraries.get(name);190if (cached != null) {191return cached;192}193194// cannot be loaded by other class loaders195if (loadedLibraryNames.contains(name)) {196throw new UnsatisfiedLinkError("Native Library " + name +197" already loaded in another classloader");198}199200/*201* When a library is being loaded, JNI_OnLoad function can cause202* another loadLibrary invocation that should succeed.203*204* We use a static stack to hold the list of libraries we are205* loading because this can happen only when called by the206* same thread because this block is synchronous.207*208* If there is a pending load operation for the library, we209* immediately return success; otherwise, we raise210* UnsatisfiedLinkError.211*/212for (NativeLibraryImpl lib : nativeLibraryContext) {213if (name.equals(lib.name())) {214if (loader == lib.fromClass.getClassLoader()) {215return lib;216} else {217throw new UnsatisfiedLinkError("Native Library " +218name + " is being loaded in another classloader");219}220}221}222223NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin, isJNI);224// load the native library225nativeLibraryContext.push(lib);226try {227if (!lib.open()) {228return null; // fail to open the native library229}230// auto unloading is only supported for JNI native libraries231// loaded by custom class loaders that can be unloaded.232// built-in class loaders are never unloaded.233boolean autoUnload = isJNI && !VM.isSystemDomainLoader(loader)234&& loader != ClassLoaders.appClassLoader();235if (autoUnload) {236// register the loaded native library for auto unloading237// when the class loader is reclaimed, all native libraries238// loaded that class loader will be unloaded.239// The entries in the libraries map are not removed since240// the entire map will be reclaimed altogether.241CleanerFactory.cleaner().register(loader, lib.unloader());242}243} finally {244nativeLibraryContext.pop();245}246// register the loaded native library247loadedLibraryNames.add(name);248libraries.put(name, lib);249return lib;250}251}252253/**254* Loads a native library from the system library path and java library path.255*256* @param name library name257*258* @throws UnsatisfiedLinkError if the native library has already been loaded259* and registered in another NativeLibraries260*/261public NativeLibrary loadLibrary(String name) {262assert name.indexOf(File.separatorChar) < 0;263assert caller != null;264265return loadLibrary(caller, name);266}267268/**269* Loads a native library from the system library path and java library path.270*271* @param name library name272* @param fromClass the caller class calling System::loadLibrary273*274* @throws UnsatisfiedLinkError if the native library has already been loaded275* and registered in another NativeLibraries276*/277public NativeLibrary loadLibrary(Class<?> fromClass, String name) {278assert name.indexOf(File.separatorChar) < 0;279280NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name);281if (lib == null && searchJavaLibraryPath) {282lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name);283}284return lib;285}286287/**288* Unloads the given native library289*290* @param lib native library291*/292public void unload(NativeLibrary lib) {293if (isJNI) {294throw new UnsupportedOperationException("explicit unloading cannot be used with auto unloading");295}296Objects.requireNonNull(lib);297synchronized (loadedLibraryNames) {298NativeLibraryImpl nl = libraries.remove(lib.name());299if (nl != lib) {300throw new IllegalArgumentException(lib.name() + " not loaded by this NativeLibraries instance");301}302// unload the native library and also remove from the global name registry303nl.unloader().run();304}305}306307private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) {308for (String path : paths) {309File libfile = new File(path, System.mapLibraryName(name));310NativeLibrary nl = loadLibrary(fromClass, libfile);311if (nl != null) {312return nl;313}314libfile = ClassLoaderHelper.mapAlternativeName(libfile);315if (libfile != null) {316nl = loadLibrary(fromClass, libfile);317if (nl != null) {318return nl;319}320}321}322return null;323}324325/**326* NativeLibraryImpl denotes a loaded native library instance.327* Each NativeLibraries contains a map of loaded native libraries in the328* private field {@code libraries}.329*330* Every native library requires a particular version of JNI. This is331* denoted by the private {@code jniVersion} field. This field is set by332* the VM when it loads the library, and used by the VM to pass the correct333* version of JNI to the native methods.334*/335static class NativeLibraryImpl implements NativeLibrary {336// the class from which the library is loaded, also indicates337// the loader this native library belongs.338final Class<?> fromClass;339// the canonicalized name of the native library.340// or static library name341final String name;342// Indicates if the native library is linked into the VM343final boolean isBuiltin;344// Indicate if this is JNI native library345final boolean isJNI;346347// opaque handle to native library, used in native code.348long handle;349// the version of JNI environment the native library requires.350int jniVersion;351352NativeLibraryImpl(Class<?> fromClass, String name, boolean isBuiltin, boolean isJNI) {353assert !isBuiltin || isJNI : "a builtin native library must be JNI library";354355this.fromClass = fromClass;356this.name = name;357this.isBuiltin = isBuiltin;358this.isJNI = isJNI;359}360361@Override362public String name() {363return name;364}365366@Override367public long find(String name) {368return findEntry0(this, name);369}370371Runnable unloader() {372return new Unloader(name, handle, isBuiltin, isJNI);373}374375/*376* Loads the named native library377*/378boolean open() {379if (handle != 0) {380throw new InternalError("Native library " + name + " has been loaded");381}382383return load(this, name, isBuiltin, isJNI);384}385}386387/*388* The run() method will be invoked when this class loader becomes389* phantom reachable to unload the native library.390*/391static class Unloader implements Runnable {392// This represents the context when a native library is unloaded393// and getFromClass() will return null,394static final NativeLibraryImpl UNLOADER =395new NativeLibraryImpl(null, "dummy", false, false);396397final String name;398final long handle;399final boolean isBuiltin;400final boolean isJNI;401402Unloader(String name, long handle, boolean isBuiltin, boolean isJNI) {403assert !isBuiltin || isJNI : "a builtin native library must be JNI library";404if (handle == 0) {405throw new IllegalArgumentException(406"Invalid handle for native library " + name);407}408409this.name = name;410this.handle = handle;411this.isBuiltin = isBuiltin;412this.isJNI = isJNI;413}414415@Override416public void run() {417synchronized (loadedLibraryNames) {418/* remove the native library name */419if (!loadedLibraryNames.remove(name)) {420throw new IllegalStateException(name + " has already been unloaded");421}422nativeLibraryContext.push(UNLOADER);423try {424unload(name, isBuiltin, isJNI, handle);425} finally {426nativeLibraryContext.pop();427}428}429}430}431432/*433* Holds system and user library paths derived from the434* {@code java.library.path} and {@code sun.boot.library.path} system435* properties. The system properties are eagerly read at bootstrap, then436* lazily parsed on first use to avoid initialization ordering issues.437*/438static class LibraryPaths {439// The paths searched for libraries440static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath());441static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath());442}443444// All native libraries we've loaded.445// This also serves as the lock to obtain nativeLibraries446// and write to nativeLibraryContext.447private static final Set<String> loadedLibraryNames = new HashSet<>();448449// native libraries being loaded450private static Deque<NativeLibraryImpl> nativeLibraryContext = new ArrayDeque<>(8);451452// Invoked in the VM to determine the context class in JNI_OnLoad453// and JNI_OnUnload454private static Class<?> getFromClass() {455if (nativeLibraryContext.isEmpty()) { // only default library456return Object.class;457}458return nativeLibraryContext.peek().fromClass;459}460461// JNI FindClass expects the caller class if invoked from JNI_OnLoad462// and JNI_OnUnload is NativeLibrary class463private static native boolean load(NativeLibraryImpl impl, String name, boolean isBuiltin, boolean isJNI);464private static native void unload(String name, boolean isBuiltin, boolean isJNI, long handle);465private static native String findBuiltinLib(String name);466private static native long findEntry0(NativeLibraryImpl lib, String name);467}468469470