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