Path: blob/master/src/java.base/share/classes/jdk/internal/loader/NativeLibraries.java
67771 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 {55private static final boolean loadLibraryOnlyIfPresent = ClassLoaderHelper.loadLibraryOnlyIfPresent();56private 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 the given145* library is determined to be non-loadable, which is system-dependent.146*147* @param fromClass the caller class calling System::loadLibrary148* @param file the path of the native library149* @throws UnsatisfiedLinkError if any error in loading the native library150*/151@SuppressWarnings("removal")152public NativeLibrary loadLibrary(Class<?> fromClass, File file) {153// Check to see if we're attempting to access a static library154String name = findBuiltinLib(file.getName());155boolean isBuiltin = (name != null);156if (!isBuiltin) {157name = AccessController.doPrivileged(new PrivilegedAction<>() {158public String run() {159try {160if (loadLibraryOnlyIfPresent && !file.exists()) {161return null;162}163return file.getCanonicalPath();164} catch (IOException e) {165return null;166}167}168});169if (name == null) {170return null;171}172}173return loadLibrary(fromClass, name, isBuiltin);174}175176/**177* Returns a NativeLibrary of the given name.178*179* @param fromClass the caller class calling System::loadLibrary180* @param name library name181* @param isBuiltin built-in library182* @throws UnsatisfiedLinkError if the native library has already been loaded183* and registered in another NativeLibraries184*/185private NativeLibrary loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) {186ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader();187if (this.loader != loader) {188throw new InternalError(fromClass.getName() + " not allowed to load library");189}190191synchronized (loadedLibraryNames) {192// find if this library has already been loaded and registered in this NativeLibraries193NativeLibrary cached = libraries.get(name);194if (cached != null) {195return cached;196}197198// cannot be loaded by other class loaders199if (loadedLibraryNames.contains(name)) {200throw new UnsatisfiedLinkError("Native Library " + name +201" already loaded in another classloader");202}203204/*205* When a library is being loaded, JNI_OnLoad function can cause206* another loadLibrary invocation that should succeed.207*208* We use a static stack to hold the list of libraries we are209* loading because this can happen only when called by the210* same thread because this block is synchronous.211*212* If there is a pending load operation for the library, we213* immediately return success; otherwise, we raise214* UnsatisfiedLinkError.215*/216for (NativeLibraryImpl lib : nativeLibraryContext) {217if (name.equals(lib.name())) {218if (loader == lib.fromClass.getClassLoader()) {219return lib;220} else {221throw new UnsatisfiedLinkError("Native Library " +222name + " is being loaded in another classloader");223}224}225}226227NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin, isJNI);228// load the native library229nativeLibraryContext.push(lib);230try {231if (!lib.open()) {232return null; // fail to open the native library233}234// auto unloading is only supported for JNI native libraries235// loaded by custom class loaders that can be unloaded.236// built-in class loaders are never unloaded.237boolean autoUnload = isJNI && !VM.isSystemDomainLoader(loader)238&& loader != ClassLoaders.appClassLoader();239if (autoUnload) {240// register the loaded native library for auto unloading241// when the class loader is reclaimed, all native libraries242// loaded that class loader will be unloaded.243// The entries in the libraries map are not removed since244// the entire map will be reclaimed altogether.245CleanerFactory.cleaner().register(loader, lib.unloader());246}247} finally {248nativeLibraryContext.pop();249}250// register the loaded native library251loadedLibraryNames.add(name);252libraries.put(name, lib);253return lib;254}255}256257/**258* Loads a native library from the system library path and java library path.259*260* @param name library name261*262* @throws UnsatisfiedLinkError if the native library has already been loaded263* and registered in another NativeLibraries264*/265public NativeLibrary loadLibrary(String name) {266assert name.indexOf(File.separatorChar) < 0;267assert caller != null;268269return loadLibrary(caller, name);270}271272/**273* Loads a native library from the system library path and java library path.274*275* @param name library name276* @param fromClass the caller class calling System::loadLibrary277*278* @throws UnsatisfiedLinkError if the native library has already been loaded279* and registered in another NativeLibraries280*/281public NativeLibrary loadLibrary(Class<?> fromClass, String name) {282assert name.indexOf(File.separatorChar) < 0;283284NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name);285if (lib == null && searchJavaLibraryPath) {286lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name);287}288return lib;289}290291/**292* Unloads the given native library293*294* @param lib native library295*/296public void unload(NativeLibrary lib) {297if (isJNI) {298throw new UnsupportedOperationException("explicit unloading cannot be used with auto unloading");299}300Objects.requireNonNull(lib);301synchronized (loadedLibraryNames) {302NativeLibraryImpl nl = libraries.remove(lib.name());303if (nl != lib) {304throw new IllegalArgumentException(lib.name() + " not loaded by this NativeLibraries instance");305}306// unload the native library and also remove from the global name registry307nl.unloader().run();308}309}310311private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) {312for (String path : paths) {313File libfile = new File(path, System.mapLibraryName(name));314NativeLibrary nl = loadLibrary(fromClass, libfile);315if (nl != null) {316return nl;317}318libfile = ClassLoaderHelper.mapAlternativeName(libfile);319if (libfile != null) {320nl = loadLibrary(fromClass, libfile);321if (nl != null) {322return nl;323}324}325}326return null;327}328329/**330* NativeLibraryImpl denotes a loaded native library instance.331* Each NativeLibraries contains a map of loaded native libraries in the332* private field {@code libraries}.333*334* Every native library requires a particular version of JNI. This is335* denoted by the private {@code jniVersion} field. This field is set by336* the VM when it loads the library, and used by the VM to pass the correct337* version of JNI to the native methods.338*/339static class NativeLibraryImpl implements NativeLibrary {340// the class from which the library is loaded, also indicates341// the loader this native library belongs.342final Class<?> fromClass;343// the canonicalized name of the native library.344// or static library name345final String name;346// Indicates if the native library is linked into the VM347final boolean isBuiltin;348// Indicate if this is JNI native library349final boolean isJNI;350351// opaque handle to native library, used in native code.352long handle;353// the version of JNI environment the native library requires.354int jniVersion;355356NativeLibraryImpl(Class<?> fromClass, String name, boolean isBuiltin, boolean isJNI) {357assert !isBuiltin || isJNI : "a builtin native library must be JNI library";358359this.fromClass = fromClass;360this.name = name;361this.isBuiltin = isBuiltin;362this.isJNI = isJNI;363}364365@Override366public String name() {367return name;368}369370@Override371public long find(String name) {372return findEntry0(this, name);373}374375Runnable unloader() {376return new Unloader(name, handle, isBuiltin, isJNI);377}378379/*380* Loads the named native library381*/382boolean open() {383if (handle != 0) {384throw new InternalError("Native library " + name + " has been loaded");385}386387return load(this, name, isBuiltin, isJNI, loadLibraryOnlyIfPresent);388}389}390391/*392* The run() method will be invoked when this class loader becomes393* phantom reachable to unload the native library.394*/395static class Unloader implements Runnable {396// This represents the context when a native library is unloaded397// and getFromClass() will return null,398static final NativeLibraryImpl UNLOADER =399new NativeLibraryImpl(null, "dummy", false, false);400401final String name;402final long handle;403final boolean isBuiltin;404final boolean isJNI;405406Unloader(String name, long handle, boolean isBuiltin, boolean isJNI) {407assert !isBuiltin || isJNI : "a builtin native library must be JNI library";408if (handle == 0) {409throw new IllegalArgumentException(410"Invalid handle for native library " + name);411}412413this.name = name;414this.handle = handle;415this.isBuiltin = isBuiltin;416this.isJNI = isJNI;417}418419@Override420public void run() {421synchronized (loadedLibraryNames) {422/* remove the native library name */423if (!loadedLibraryNames.remove(name)) {424throw new IllegalStateException(name + " has already been unloaded");425}426nativeLibraryContext.push(UNLOADER);427try {428unload(name, isBuiltin, isJNI, handle);429} finally {430nativeLibraryContext.pop();431}432}433}434}435436/*437* Holds system and user library paths derived from the438* {@code java.library.path} and {@code sun.boot.library.path} system439* properties. The system properties are eagerly read at bootstrap, then440* lazily parsed on first use to avoid initialization ordering issues.441*/442static class LibraryPaths {443// The paths searched for libraries444static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath());445static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath());446}447448// All native libraries we've loaded.449// This also serves as the lock to obtain nativeLibraries450// and write to nativeLibraryContext.451private static final Set<String> loadedLibraryNames = new HashSet<>();452453// native libraries being loaded454private static Deque<NativeLibraryImpl> nativeLibraryContext = new ArrayDeque<>(8);455456// Invoked in the VM to determine the context class in JNI_OnLoad457// and JNI_OnUnload458private static Class<?> getFromClass() {459if (nativeLibraryContext.isEmpty()) { // only default library460return Object.class;461}462return nativeLibraryContext.peek().fromClass;463}464465// JNI FindClass expects the caller class if invoked from JNI_OnLoad466// and JNI_OnUnload is NativeLibrary class467private static native boolean load(NativeLibraryImpl impl, String name,468boolean isBuiltin, boolean isJNI,469boolean throwExceptionIfFail);470private static native void unload(String name, boolean isBuiltin, boolean isJNI, long handle);471private static native String findBuiltinLib(String name);472private static native long findEntry0(NativeLibraryImpl lib, String name);473}474475476