1 /* 2 * Copyright (c) 2020, 2025, Oracle and/or its affiliates. All rights reserved. 3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. 4 * 5 * This code is free software; you can redistribute it and/or modify it 6 * under the terms of the GNU General Public License version 2 only, as 7 * published by the Free Software Foundation. Oracle designates this 8 * particular file as subject to the "Classpath" exception as provided 9 * by Oracle in the LICENSE file that accompanied this code. 10 * 11 * This code is distributed in the hope that it will be useful, but WITHOUT 12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 13 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License 14 * version 2 for more details (a copy is included in the LICENSE file that 15 * accompanied this code). 16 * 17 * You should have received a copy of the GNU General Public License version 18 * 2 along with this work; if not, write to the Free Software Foundation, 19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. 20 * 21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA 22 * or visit www.oracle.com if you need additional information or have any 23 * questions. 24 */ 25 package jdk.internal.loader; 26 27 import jdk.internal.misc.VM; 28 import jdk.internal.ref.CleanerFactory; 29 import jdk.internal.util.StaticProperty; 30 31 import java.io.File; 32 import java.io.IOException; 33 import java.security.AccessController; 34 import java.security.PrivilegedAction; 35 import java.util.ArrayDeque; 36 import java.util.Deque; 37 import java.util.function.BiFunction; 38 import java.util.function.Function; 39 import java.util.Map; 40 import java.util.Set; 41 import java.util.concurrent.ConcurrentHashMap; 42 import java.util.concurrent.locks.ReentrantLock; 43 44 /** 45 * Native libraries are loaded via {@link System#loadLibrary(String)}, 46 * {@link System#load(String)}, {@link Runtime#loadLibrary(String)} and 47 * {@link Runtime#load(String)}. They are caller-sensitive. 48 * 49 * Each class loader has a NativeLibraries instance to register all of its 50 * loaded native libraries. System::loadLibrary (and other APIs) only 51 * allows a native library to be loaded by one class loader, i.e. one 52 * NativeLibraries instance. Any attempt to load a native library that 53 * has already been loaded by a class loader with another class loader 54 * will fail. 55 */ 56 public final class NativeLibraries { 57 private static final boolean loadLibraryOnlyIfPresent = ClassLoaderHelper.loadLibraryOnlyIfPresent(); 58 private final Map<String, NativeLibraryImpl> libraries = new ConcurrentHashMap<>(); 59 private final ClassLoader loader; 60 // caller, if non-null, is the fromClass parameter for NativeLibraries::loadLibrary 61 // unless specified 62 private final Class<?> caller; // may be null 63 private final boolean searchJavaLibraryPath; 64 65 /** 66 * Creates a NativeLibraries instance for loading JNI native libraries 67 * via for System::loadLibrary use. 68 * 69 * 1. Support of auto-unloading. The loaded native libraries are unloaded 70 * 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 */ 81 public static NativeLibraries newInstance(ClassLoader loader) { 82 return new NativeLibraries(loader, loader != null ? null : NativeLibraries.class, loader != null); 83 } 84 85 private NativeLibraries(ClassLoader loader, Class<?> caller, boolean searchJavaLibraryPath) { 86 this.loader = loader; 87 this.caller = caller; 88 this.searchJavaLibraryPath = searchJavaLibraryPath; 89 } 90 91 /* 92 * Find the address of the given symbol name from the native libraries 93 * loaded in this NativeLibraries instance. 94 */ 95 public long find(String name) { 96 if (libraries.isEmpty()) 97 return 0; 98 99 // the native libraries map may be updated in another thread 100 // when a native library is being loaded. No symbol will be 101 // searched from it yet. 102 for (NativeLibrary lib : libraries.values()) { 103 long entry = lib.find(name); 104 if (entry != 0) return entry; 105 } 106 return 0; 107 } 108 109 /* 110 * Load a native library from the given file. Returns null if the given 111 * library is determined to be non-loadable, which is system-dependent. 112 * 113 * @param fromClass the caller class calling System::loadLibrary 114 * @param file the path of the native library 115 * @throws UnsatisfiedLinkError if any error in loading the native library 116 */ 117 public NativeLibrary loadLibrary(Class<?> fromClass, File file) { 118 // Check to see if we're attempting to access a static library 119 String name = findBuiltinLib(file.getName()); 120 boolean isBuiltin = (name != null); 121 if (!isBuiltin) { 122 try { 123 if (loadLibraryOnlyIfPresent && !file.exists()) { 124 return null; 125 } 126 name = file.getCanonicalPath(); 127 } catch (IOException e) { 128 return null; 129 } 130 } 131 return loadLibrary(fromClass, name, isBuiltin); 132 } 133 134 /** 135 * Returns a NativeLibrary of the given name. 136 * 137 * @param fromClass the caller class calling System::loadLibrary 138 * @param name library name 139 * @param isBuiltin built-in library 140 * @throws UnsatisfiedLinkError if the native library has already been loaded 141 * and registered in another NativeLibraries 142 */ 143 private NativeLibrary loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) { 144 ClassLoader loader = (fromClass == null) ? null : fromClass.getClassLoader(); 145 if (this.loader != loader) { 146 throw new InternalError(fromClass.getName() + " not allowed to load library"); 147 } 148 149 acquireNativeLibraryLock(name); 150 try { 151 // find if this library has already been loaded and registered in this NativeLibraries 152 NativeLibrary cached = libraries.get(name); 153 if (cached != null) { 154 return cached; 155 } 156 157 // cannot be loaded by other class loaders 158 if (Holder.loadedLibraryNames.contains(name)) { 159 throw new UnsatisfiedLinkError("Native Library " + name + 160 " already loaded in another classloader"); 161 } 162 163 /* 164 * When a library is being loaded, JNI_OnLoad function can cause 165 * another loadLibrary invocation that should succeed. 166 * 167 * Each thread maintains its own stack to hold the list of 168 * libraries it is loading. 169 * 170 * If there is a pending load operation for the library, we 171 * immediately return success; if the pending load is from 172 * a different class loader, we raise UnsatisfiedLinkError. 173 */ 174 for (NativeLibraryImpl lib : NativeLibraryContext.current()) { 175 if (name.equals(lib.name())) { 176 if (loader == lib.fromClass.getClassLoader()) { 177 return lib; 178 } else { 179 throw new UnsatisfiedLinkError("Native Library " + 180 name + " is being loaded in another classloader"); 181 } 182 } 183 } 184 185 NativeLibraryImpl lib = new NativeLibraryImpl(fromClass, name, isBuiltin); 186 // load the native library 187 NativeLibraryContext.push(lib); 188 try { 189 if (!lib.open()) { 190 return null; // fail to open the native library 191 } 192 // auto unloading is only supported for JNI native libraries 193 // loaded by custom class loaders that can be unloaded. 194 // built-in class loaders are never unloaded. 195 boolean autoUnload = !VM.isSystemDomainLoader(loader) && loader != ClassLoaders.appClassLoader(); 196 if (autoUnload) { 197 // register the loaded native library for auto unloading 198 // when the class loader is reclaimed, all native libraries 199 // loaded that class loader will be unloaded. 200 // The entries in the libraries map are not removed since 201 // the entire map will be reclaimed altogether. 202 CleanerFactory.cleaner().register(loader, lib.unloader()); 203 } 204 } finally { 205 NativeLibraryContext.pop(); 206 } 207 // register the loaded native library 208 Holder.loadedLibraryNames.add(name); 209 libraries.put(name, lib); 210 return lib; 211 } finally { 212 releaseNativeLibraryLock(name); 213 } 214 } 215 216 /** 217 * Loads a native library from the system library path and java library path. 218 * 219 * @param name library name 220 * 221 * @throws UnsatisfiedLinkError if the native library has already been loaded 222 * and registered in another NativeLibraries 223 */ 224 public NativeLibrary loadLibrary(String name) { 225 assert name.indexOf(File.separatorChar) < 0; 226 return loadLibrary(caller, name); 227 } 228 229 /** 230 * Loads a native library from the system library path and java library path. 231 * 232 * @param name library name 233 * @param fromClass the caller class calling System::loadLibrary 234 * 235 * @throws UnsatisfiedLinkError if the native library has already been loaded 236 * and registered in another NativeLibraries 237 */ 238 public NativeLibrary loadLibrary(Class<?> fromClass, String name) { 239 assert name.indexOf(File.separatorChar) < 0; 240 241 NativeLibrary lib = findFromPaths(LibraryPaths.SYS_PATHS, fromClass, name); 242 if (lib == null && searchJavaLibraryPath) { 243 lib = findFromPaths(LibraryPaths.USER_PATHS, fromClass, name); 244 } 245 return lib; 246 } 247 248 // Called at the end of AOTCache assembly phase. 249 public void clear() { 250 libraries.clear(); 251 } 252 253 private NativeLibrary findFromPaths(String[] paths, Class<?> fromClass, String name) { 254 for (String path : paths) { 255 File libfile = new File(path, System.mapLibraryName(name)); 256 NativeLibrary nl = loadLibrary(fromClass, libfile); 257 if (nl != null) { 258 return nl; 259 } 260 libfile = ClassLoaderHelper.mapAlternativeName(libfile); 261 if (libfile != null) { 262 nl = loadLibrary(fromClass, libfile); 263 if (nl != null) { 264 return nl; 265 } 266 } 267 } 268 return null; 269 } 270 271 /** 272 * NativeLibraryImpl denotes a loaded native library instance. 273 * Each NativeLibraries contains a map of loaded native libraries in the 274 * private field {@code libraries}. 275 * 276 * Every native library requires a particular version of JNI. This is 277 * denoted by the private {@code jniVersion} field. This field is set by 278 * the VM when it loads the library, and used by the VM to pass the correct 279 * version of JNI to the native methods. 280 */ 281 static class NativeLibraryImpl extends NativeLibrary { 282 // the class from which the library is loaded, also indicates 283 // the loader this native library belongs. 284 final Class<?> fromClass; 285 // the canonicalized name of the native library. 286 // or static library name 287 final String name; 288 // Indicates if the native library is linked into the VM 289 final boolean isBuiltin; 290 291 // opaque handle to native library, used in native code. 292 long handle; 293 // the version of JNI environment the native library requires. 294 int jniVersion; 295 296 NativeLibraryImpl(Class<?> fromClass, String name, boolean isBuiltin) { 297 this.fromClass = fromClass; 298 this.name = name; 299 this.isBuiltin = isBuiltin; 300 } 301 302 @Override 303 public String name() { 304 return name; 305 } 306 307 @Override 308 public long find(String name) { 309 return findEntry0(handle, name); 310 } 311 312 /* 313 * Unloader::run method is invoked to unload the native library 314 * when this class loader becomes phantom reachable. 315 */ 316 private Runnable unloader() { 317 return new Unloader(name, handle, isBuiltin); 318 } 319 320 /* 321 * Loads the named native library 322 */ 323 boolean open() { 324 if (handle != 0) { 325 throw new InternalError("Native library " + name + " has been loaded"); 326 } 327 328 return load(this, name, isBuiltin, throwExceptionIfFail()); 329 } 330 331 @SuppressWarnings("removal") 332 private boolean throwExceptionIfFail() { 333 if (loadLibraryOnlyIfPresent) return true; 334 335 // If the file exists but fails to load, UnsatisfiedLinkException thrown by the VM 336 // will include the error message from dlopen to provide diagnostic information 337 return AccessController.doPrivileged(new PrivilegedAction<>() { 338 public Boolean run() { 339 File file = new File(name); 340 return file.exists(); 341 } 342 }); 343 } 344 345 /* 346 * Close this native library. 347 */ 348 void close() { 349 unload(name, isBuiltin, handle); 350 } 351 } 352 353 /* 354 * The run() method will be invoked when this class loader becomes 355 * phantom reachable to unload the native library. 356 */ 357 static class Unloader implements Runnable { 358 // This represents the context when a native library is unloaded 359 // and getFromClass() will return null, 360 static final NativeLibraryImpl UNLOADER = 361 new NativeLibraryImpl(null, "dummy", false); 362 363 final String name; 364 final long handle; 365 final boolean isBuiltin; 366 367 Unloader(String name, long handle, boolean isBuiltin) { 368 if (handle == 0) { 369 throw new IllegalArgumentException( 370 "Invalid handle for native library " + name); 371 } 372 373 this.name = name; 374 this.handle = handle; 375 this.isBuiltin = isBuiltin; 376 } 377 378 @Override 379 public void run() { 380 acquireNativeLibraryLock(name); 381 try { 382 /* remove the native library name */ 383 if (!Holder.loadedLibraryNames.remove(name)) { 384 throw new IllegalStateException(name + " has already been unloaded"); 385 } 386 NativeLibraryContext.push(UNLOADER); 387 try { 388 unload(name, isBuiltin, handle); 389 } finally { 390 NativeLibraryContext.pop(); 391 } 392 } finally { 393 releaseNativeLibraryLock(name); 394 } 395 } 396 } 397 398 /* 399 * Holds system and user library paths derived from the 400 * {@code java.library.path} and {@code sun.boot.library.path} system 401 * properties. The system properties are eagerly read at bootstrap, then 402 * lazily parsed on first use to avoid initialization ordering issues. 403 */ 404 static class LibraryPaths { 405 // The paths searched for libraries 406 static final String[] SYS_PATHS = ClassLoaderHelper.parsePath(StaticProperty.sunBootLibraryPath()); 407 static final String[] USER_PATHS = ClassLoaderHelper.parsePath(StaticProperty.javaLibraryPath()); 408 } 409 410 // Holder has the fields that need to be initialized during JVM bootstrap even if 411 // the outer is aot-initialized. 412 static class Holder { 413 // All native libraries we've loaded. 414 private static final Set<String> loadedLibraryNames = 415 ConcurrentHashMap.newKeySet(); 416 } 417 418 // reentrant lock class that allows exact counting (with external synchronization) 419 @SuppressWarnings("serial") 420 private static final class CountedLock extends ReentrantLock { 421 422 private int counter = 0; 423 424 public void increment() { 425 if (counter == Integer.MAX_VALUE) { 426 // prevent overflow 427 throw new Error("Maximum lock count exceeded"); 428 } 429 ++counter; 430 } 431 432 public void decrement() { 433 --counter; 434 } 435 436 public int getCounter() { 437 return counter; 438 } 439 } 440 441 // Maps native library name to the corresponding lock object 442 private static final Map<String, CountedLock> nativeLibraryLockMap = 443 new ConcurrentHashMap<>(); 444 445 private static void acquireNativeLibraryLock(String libraryName) { 446 nativeLibraryLockMap.compute(libraryName, 447 new BiFunction<>() { 448 public CountedLock apply(String name, CountedLock currentLock) { 449 if (currentLock == null) { 450 currentLock = new CountedLock(); 451 } 452 // safe as compute BiFunction<> is executed atomically 453 currentLock.increment(); 454 return currentLock; 455 } 456 } 457 ).lock(); 458 } 459 460 private static void releaseNativeLibraryLock(String libraryName) { 461 CountedLock lock = nativeLibraryLockMap.computeIfPresent(libraryName, 462 new BiFunction<>() { 463 public CountedLock apply(String name, CountedLock currentLock) { 464 if (currentLock.getCounter() == 1) { 465 // unlock and release the object if no other threads are queued 466 currentLock.unlock(); 467 // remove the element 468 return null; 469 } else { 470 currentLock.decrement(); 471 return currentLock; 472 } 473 } 474 } 475 ); 476 if (lock != null) { 477 lock.unlock(); 478 } 479 } 480 481 // native libraries being loaded 482 private static final class NativeLibraryContext { 483 484 // Maps thread object to the native library context stack, maintained by each thread 485 private static Map<Thread, Deque<NativeLibraryImpl>> nativeLibraryThreadContext = 486 new ConcurrentHashMap<>(); 487 488 // returns a context associated with the current thread 489 private static Deque<NativeLibraryImpl> current() { 490 return nativeLibraryThreadContext.computeIfAbsent( 491 Thread.currentThread(), 492 new Function<>() { 493 public Deque<NativeLibraryImpl> apply(Thread t) { 494 return new ArrayDeque<>(8); 495 } 496 }); 497 } 498 499 private static NativeLibraryImpl peek() { 500 return current().peek(); 501 } 502 503 private static void push(NativeLibraryImpl lib) { 504 current().push(lib); 505 } 506 507 private static void pop() { 508 // this does not require synchronization since each 509 // thread has its own context 510 Deque<NativeLibraryImpl> libs = current(); 511 libs.pop(); 512 if (libs.isEmpty()) { 513 // context can be safely removed once empty 514 nativeLibraryThreadContext.remove(Thread.currentThread()); 515 } 516 } 517 518 private static boolean isEmpty() { 519 Deque<NativeLibraryImpl> context = 520 nativeLibraryThreadContext.get(Thread.currentThread()); 521 return (context == null || context.isEmpty()); 522 } 523 } 524 525 // Invoked in the VM to determine the context class in JNI_OnLoad 526 // and JNI_OnUnload 527 private static Class<?> getFromClass() { 528 if (NativeLibraryContext.isEmpty()) { // only default library 529 return Object.class; 530 } 531 return NativeLibraryContext.peek().fromClass; 532 } 533 534 /* 535 * Return true if the given library is successfully loaded. 536 * If the given library cannot be loaded for any reason, 537 * if throwExceptionIfFail is false, then this method returns false; 538 * otherwise, UnsatisfiedLinkError will be thrown. 539 * 540 * JNI FindClass expects the caller class if invoked from JNI_OnLoad 541 * and JNI_OnUnload is NativeLibrary class. 542 */ 543 private static native boolean load(NativeLibraryImpl impl, String name, 544 boolean isBuiltin, 545 boolean throwExceptionIfFail); 546 /* 547 * Unload the named library. JNI_OnUnload, if present, will be invoked 548 * before the native library is unloaded. 549 */ 550 private static native void unload(String name, boolean isBuiltin, long handle); 551 private static native String findBuiltinLib(String name); 552 }