8164512: Replace ClassLoader use of finalizer with phantom reference to unload native library
Reviewed-by: alanb, bchristi, kbarrett, dholmes, plevart
--- a/make/mapfiles/libjava/mapfile-vers Mon Nov 06 17:35:40 2017 -0500
+++ b/make/mapfiles/libjava/mapfile-vers Mon Nov 06 17:48:00 2017 -0800
@@ -130,8 +130,8 @@
Java_java_lang_ClassLoader_defineClass2;
Java_java_lang_ClassLoader_findBuiltinLib;
Java_java_lang_ClassLoader_findLoadedClass0;
- Java_java_lang_ClassLoader_00024NativeLibrary_find;
- Java_java_lang_ClassLoader_00024NativeLibrary_load;
+ Java_java_lang_ClassLoader_00024NativeLibrary_findEntry;
+ Java_java_lang_ClassLoader_00024NativeLibrary_load0;
Java_java_lang_ClassLoader_00024NativeLibrary_unload;
Java_java_lang_ClassLoader_registerNatives;
Java_java_lang_Double_longBitsToDouble;
--- a/make/mapfiles/libjava/reorder-sparc Mon Nov 06 17:35:40 2017 -0500
+++ b/make/mapfiles/libjava/reorder-sparc Mon Nov 06 17:48:00 2017 -0800
@@ -48,8 +48,8 @@
text: .text%Java_java_io_FileInputStream_close0;
text: .text%Java_java_lang_System_mapLibraryName;
text: .text%Java_java_io_UnixFileSystem_getBooleanAttributes0;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_find;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load0;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_findEntry;
text: .text%Java_java_security_AccessController_doPrivileged__Ljava_security_PrivilegedExceptionAction_2;
text: .text%Java_java_io_UnixFileSystem_list;
text: .text%JNU_ClassString;
--- a/make/mapfiles/libjava/reorder-sparcv9 Mon Nov 06 17:35:40 2017 -0500
+++ b/make/mapfiles/libjava/reorder-sparcv9 Mon Nov 06 17:48:00 2017 -0800
@@ -57,8 +57,8 @@
text: .text%Java_java_io_UnixFileSystem_getBooleanAttributes0;
text: .text%Java_java_security_AccessController_doPrivileged__Ljava_security_PrivilegedExceptionAction_2Ljava_security_AccessControlContext_2;
text: .text%Java_java_lang_System_mapLibraryName;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_find;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load0;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_findEntry;
text: .text%Java_java_io_UnixFileSystem_getLength;
text: .text%Java_java_lang_Object_getClass;
text: .text%Java_java_lang_ClassLoader_defineClass0;
--- a/make/mapfiles/libjava/reorder-x86 Mon Nov 06 17:35:40 2017 -0500
+++ b/make/mapfiles/libjava/reorder-x86 Mon Nov 06 17:48:00 2017 -0800
@@ -50,8 +50,8 @@
text: .text%Java_java_security_AccessController_doPrivileged__Ljava_security_PrivilegedExceptionAction_2Ljava_security_AccessControlContext_2;
text: .text%Java_java_lang_System_mapLibraryName;
text: .text%cpchars: OUTPUTDIR/System.o;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load;
-text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_find;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_load0;
+text: .text%Java_java_lang_ClassLoader_00024NativeLibrary_findEntry;
text: .text%Java_java_lang_Float_floatToRawIntBits;
text: .text%Java_java_lang_Double_doubleToRawLongBits;
text: .text%Java_java_io_FileInputStream_open0;
--- a/make/test/JtregNativeJdk.gmk Mon Nov 06 17:35:40 2017 -0500
+++ b/make/test/JtregNativeJdk.gmk Mon Nov 06 17:48:00 2017 -0800
@@ -44,6 +44,7 @@
# Add more directories here when needed.
BUILD_JDK_JTREG_NATIVE_SRC += \
$(TOPDIR)/test/jdk/native_sanity \
+ $(TOPDIR)/test/jdk/java/lang/ClassLoader/nativeLibrary \
$(TOPDIR)/test/jdk/java/lang/String/nativeEncoding \
#
--- a/src/java.base/share/classes/java/lang/ClassLoader.java Mon Nov 06 17:35:40 2017 -0500
+++ b/src/java.base/share/classes/java/lang/ClassLoader.java Mon Nov 06 17:48:00 2017 -0800
@@ -37,17 +37,20 @@
import java.security.PrivilegedAction;
import java.security.ProtectionDomain;
import java.security.cert.Certificate;
+import java.util.Arrays;
import java.util.Collections;
+import java.util.Deque;
import java.util.Enumeration;
import java.util.HashMap;
+import java.util.HashSet;
import java.util.Hashtable;
+import java.util.LinkedList;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Set;
import java.util.Spliterator;
import java.util.Spliterators;
-import java.util.Stack;
import java.util.Vector;
import java.util.WeakHashMap;
import java.util.concurrent.ConcurrentHashMap;
@@ -58,9 +61,9 @@
import jdk.internal.perf.PerfCounter;
import jdk.internal.loader.BootLoader;
import jdk.internal.loader.ClassLoaders;
-import jdk.internal.misc.SharedSecrets;
import jdk.internal.misc.Unsafe;
import jdk.internal.misc.VM;
+import jdk.internal.ref.CleanerFactory;
import jdk.internal.reflect.CallerSensitive;
import jdk.internal.reflect.Reflection;
import sun.reflect.misc.ReflectUtil;
@@ -2375,75 +2378,161 @@
* @since 1.2
*/
static class 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.
- private int jniVersion;
- // the class from which the library is loaded, also indicates
- // the loader this native library belongs.
- private Class<?> fromClass;
- // the canonicalized name of the native library.
- // or static library name
- String name;
- // Indicates if the native library is linked into the VM
- boolean isBuiltin;
- // Indicates if the native library is loaded
- boolean loaded;
- native void load(String name, boolean isBuiltin);
+ int jniVersion;
+
+ native boolean load0(String name, boolean isBuiltin);
- native long find(String name);
- native void unload(String name, boolean isBuiltin);
+ native long findEntry(String name);
- public NativeLibrary(Class<?> fromClass, String name, boolean isBuiltin) {
+ NativeLibrary(Class<?> fromClass, String name, boolean isBuiltin) {
this.name = name;
this.fromClass = fromClass;
this.isBuiltin = isBuiltin;
}
- @SuppressWarnings("deprecation")
- protected void finalize() {
- synchronized (loadedLibraryNames) {
- if (fromClass.getClassLoader() != null && loaded) {
- this.fromClass = null; // no context when unloaded
+ /*
+ * Loads the native library and registers for cleanup when its
+ * associated class loader is unloaded
+ */
+ boolean load() {
+ if (handle != 0) {
+ throw new InternalError("Native library " + name + " has been loaded");
+ }
+
+ if (!load0(name, isBuiltin)) return false;
+
+ // register the class loader for cleanup when unloaded
+ // built class loaders are never unloaded
+ ClassLoader loader = fromClass.getClassLoader();
+ if (loader != null &&
+ loader != getBuiltinPlatformClassLoader() &&
+ loader != getBuiltinAppClassLoader()) {
+ CleanerFactory.cleaner().register(loader,
+ new Unloader(name, handle, isBuiltin));
+ }
+ return true;
+ }
+
+ static boolean loadLibrary(Class<?> fromClass, String name, boolean isBuiltin) {
+ ClassLoader loader =
+ fromClass == null ? null : fromClass.getClassLoader();
- /* remove the native library name */
- int size = loadedLibraryNames.size();
- for (int i = 0; i < size; i++) {
- if (name.equals(loadedLibraryNames.elementAt(i))) {
- loadedLibraryNames.removeElementAt(i);
- break;
+ synchronized (loadedLibraryNames) {
+ Map<String, NativeLibrary> libs =
+ loader != null ? loader.nativeLibraries() : systemNativeLibraries();
+ if (libs.containsKey(name)) {
+ return true;
+ }
+
+ if (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.
+ *
+ * We use a static stack to hold the list of libraries we are
+ * loading because this can happen only when called by the
+ * same thread because Runtime.load and Runtime.loadLibrary
+ * are synchronous.
+ *
+ * If there is a pending load operation for the library, we
+ * immediately return success; otherwise, we raise
+ * UnsatisfiedLinkError.
+ */
+ for (NativeLibrary lib : nativeLibraryContext) {
+ if (name.equals(lib.name)) {
+ if (loader == lib.fromClass.getClassLoader()) {
+ return true;
+ } else {
+ throw new UnsatisfiedLinkError("Native Library " +
+ name + " is being loaded in another classloader");
}
}
- /* unload the library. */
- ClassLoader.nativeLibraryContext.push(this);
+ }
+ NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
+ // load the native library
+ nativeLibraryContext.push(lib);
+ try {
+ if (!lib.load()) return false;
+ } finally {
+ nativeLibraryContext.pop();
+ }
+ // register the loaded native library
+ loadedLibraryNames.add(name);
+ libs.put(name, lib);
+ }
+ return true;
+ }
+
+ // Invoked in the VM to determine the context class in JNI_OnLoad
+ // and JNI_OnUnload
+ static Class<?> getFromClass() {
+ return nativeLibraryContext.peek().fromClass;
+ }
+
+ // native libraries being loaded
+ static Deque<NativeLibrary> nativeLibraryContext = new LinkedList<>();
+
+ /*
+ * 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 NativeLibrary UNLOADER =
+ new NativeLibrary(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() {
+ synchronized (loadedLibraryNames) {
+ /* remove the native library name */
+ loadedLibraryNames.remove(name);
+ nativeLibraryContext.push(UNLOADER);
try {
- unload(name, isBuiltin);
+ unload(name, isBuiltin, handle);
} finally {
- ClassLoader.nativeLibraryContext.pop();
+ nativeLibraryContext.pop();
}
+
}
}
}
- // Invoked in the VM to determine the context class in
- // JNI_Load/JNI_Unload
- static Class<?> getFromClass() {
- return ClassLoader.nativeLibraryContext.peek().fromClass;
- }
+
+ // JNI FindClass expects the caller class if invoked from JNI_OnLoad
+ // and JNI_OnUnload is NativeLibrary class
+ static native void unload(String name, boolean isBuiltin, long handle);
}
- // All native library names we've loaded.
- private static Vector<String> loadedLibraryNames = new Vector<>();
-
- // Native libraries belonging to system classes.
- private static Vector<NativeLibrary> systemNativeLibraries
- = new Vector<>();
-
- // Native libraries associated with the class loader.
- private Vector<NativeLibrary> nativeLibraries = new Vector<>();
-
- // native libraries being loaded/unloaded.
- private static Stack<NativeLibrary> nativeLibraryContext = new Stack<>();
-
// The paths searched for libraries
private static String usr_paths[];
private static String sys_paths[];
@@ -2455,7 +2544,7 @@
int psCount = 0;
if (ClassLoaderHelper.allowsQuotedPathElements &&
- ldPath.indexOf('\"') >= 0) {
+ ldPath.indexOf('\"') >= 0) {
// First, remove quotes put around quoted parts of paths.
// Second, use a quotation mark as a new path separator.
// This will preserve any quoted old path separators.
@@ -2465,7 +2554,7 @@
char ch = ldPath.charAt(i);
if (ch == '\"') {
while (++i < ldLen &&
- (ch = ldPath.charAt(i)) != '\"') {
+ (ch = ldPath.charAt(i)) != '\"') {
buf[bufLen++] = ch;
}
} else {
@@ -2481,7 +2570,7 @@
ps = '\"';
} else {
for (int i = ldPath.indexOf(ps); i >= 0;
- i = ldPath.indexOf(ps, i + 1)) {
+ i = ldPath.indexOf(ps, i + 1)) {
psCount++;
}
}
@@ -2491,11 +2580,11 @@
for (int j = 0; j < psCount; ++j) {
int pathEnd = ldPath.indexOf(ps, pathStart);
paths[j] = (pathStart < pathEnd) ?
- ldPath.substring(pathStart, pathEnd) : ".";
+ ldPath.substring(pathStart, pathEnd) : ".";
pathStart = pathEnd + 1;
}
paths[psCount] = (pathStart < ldLen) ?
- ldPath.substring(pathStart, ldLen) : ".";
+ ldPath.substring(pathStart, ldLen) : ".";
return paths;
}
@@ -2520,7 +2609,7 @@
File libfile = new File(libfilename);
if (!libfile.isAbsolute()) {
throw new UnsatisfiedLinkError(
- "ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
+ "ClassLoader.findLibrary failed to return an absolute path: " + libfilename);
}
if (loadLibrary0(fromClass, libfile)) {
return;
@@ -2551,10 +2640,11 @@
}
}
// Oops, it failed
- throw new UnsatisfiedLinkError("no " + name + " in java.library.path");
+ throw new UnsatisfiedLinkError("no " + name +
+ " in java.library.path: " + Arrays.toString(usr_paths));
}
- static native String findBuiltinLib(String name);
+ private static native String findBuiltinLib(String name);
private static boolean loadLibrary0(Class<?> fromClass, final File file) {
// Check to see if we're attempting to access a static library
@@ -2575,85 +2665,72 @@
return false;
}
}
- ClassLoader loader =
- (fromClass == null) ? null : fromClass.getClassLoader();
- Vector<NativeLibrary> libs =
- loader != null ? loader.nativeLibraries : systemNativeLibraries;
- synchronized (libs) {
- int size = libs.size();
- for (int i = 0; i < size; i++) {
- NativeLibrary lib = libs.elementAt(i);
- if (name.equals(lib.name)) {
- return true;
- }
- }
-
- synchronized (loadedLibraryNames) {
- if (loadedLibraryNames.contains(name)) {
- throw new UnsatisfiedLinkError
- ("Native Library " +
- name +
- " already loaded in another classloader");
- }
- /* If the library is being loaded (must be by the same thread,
- * because Runtime.load and Runtime.loadLibrary are
- * synchronous). The reason is can occur is that the JNI_OnLoad
- * function can cause another loadLibrary invocation.
- *
- * Thus we can use a static stack to hold the list of libraries
- * we are loading.
- *
- * If there is a pending load operation for the library, we
- * immediately return success; otherwise, we raise
- * UnsatisfiedLinkError.
- */
- int n = nativeLibraryContext.size();
- for (int i = 0; i < n; i++) {
- NativeLibrary lib = nativeLibraryContext.elementAt(i);
- if (name.equals(lib.name)) {
- if (loader == lib.fromClass.getClassLoader()) {
- return true;
- } else {
- throw new UnsatisfiedLinkError
- ("Native Library " +
- name +
- " is being loaded in another classloader");
- }
- }
- }
- NativeLibrary lib = new NativeLibrary(fromClass, name, isBuiltin);
- nativeLibraryContext.push(lib);
- try {
- lib.load(name, isBuiltin);
- } finally {
- nativeLibraryContext.pop();
- }
- if (lib.loaded) {
- loadedLibraryNames.addElement(name);
- libs.addElement(lib);
- return true;
- }
- return false;
- }
- }
+ return NativeLibrary.loadLibrary(fromClass, name, isBuiltin);
}
- // Invoked in the VM class linking code.
- static long findNative(ClassLoader loader, String name) {
- Vector<NativeLibrary> libs =
- loader != null ? loader.nativeLibraries : systemNativeLibraries;
- synchronized (libs) {
- int size = libs.size();
- for (int i = 0; i < size; i++) {
- NativeLibrary lib = libs.elementAt(i);
- long entry = lib.find(name);
- if (entry != 0)
- return entry;
- }
+ /*
+ * Invoked in the VM class linking code.
+ */
+ private static long findNative(ClassLoader loader, String entryName) {
+ Map<String, NativeLibrary> libs =
+ loader != null ? loader.nativeLibraries() : systemNativeLibraries();
+ if (libs.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 : libs.values()) {
+ long entry = lib.findEntry(entryName);
+ if (entry != 0) return entry;
}
return 0;
}
+ // All native library names we've loaded.
+ // This also serves as the lock to obtain nativeLibraries
+ // and write to nativeLibraryContext.
+ private static final Set<String> loadedLibraryNames = new HashSet<>();
+
+ // Native libraries belonging to system classes.
+ private static volatile Map<String, NativeLibrary> systemNativeLibraries;
+
+ // Native libraries associated with the class loader.
+ private volatile Map<String, NativeLibrary> nativeLibraries;
+
+ /*
+ * Returns the native libraries map associated with bootstrap class loader
+ * This method will create the map at the first time when called.
+ */
+ private static Map<String, NativeLibrary> systemNativeLibraries() {
+ Map<String, NativeLibrary> libs = systemNativeLibraries;
+ if (libs == null) {
+ synchronized (loadedLibraryNames) {
+ libs = systemNativeLibraries;
+ if (libs == null) {
+ libs = systemNativeLibraries = new ConcurrentHashMap<>();
+ }
+ }
+ }
+ return libs;
+ }
+
+ /*
+ * Returns the native libraries map associated with this class loader
+ * This method will create the map at the first time when called.
+ */
+ private Map<String, NativeLibrary> nativeLibraries() {
+ Map<String, NativeLibrary> libs = nativeLibraries;
+ if (libs == null) {
+ synchronized (loadedLibraryNames) {
+ libs = nativeLibraries;
+ if (libs == null) {
+ libs = nativeLibraries = new ConcurrentHashMap<>();
+ }
+ }
+ }
+ return libs;
+ }
// -- Assertion management --
--- a/src/java.base/share/classes/java/lang/Runtime.java Mon Nov 06 17:35:40 2017 -0500
+++ b/src/java.base/share/classes/java/lang/Runtime.java Mon Nov 06 17:48:00 2017 -0800
@@ -765,7 +765,9 @@
* with the VM, then the JNI_OnLoad_L function exported by the library
* is invoked rather than attempting to load a dynamic library.
* A filename matching the argument does not have to exist in the file
- * system. See the JNI Specification for more details.
+ * system.
+ * See the <a href="{@docRoot}/../specs/jni/index.html"> JNI Specification</a>
+ * for more details.
*
* Otherwise, the filename argument is mapped to a native library image in
* an implementation-dependent manner.
@@ -818,7 +820,8 @@
* specific prefix, file extension or path. If a native library
* called {@code libname} is statically linked with the VM, then the
* JNI_OnLoad_{@code libname} function exported by the library is invoked.
- * See the JNI Specification for more details.
+ * See the <a href="{@docRoot}/../specs/jni/index.html"> JNI Specification</a>
+ * for more details.
*
* Otherwise, the libname argument is loaded from a system library
* location and mapped to a native library image in an implementation-
--- a/src/java.base/share/classes/java/lang/System.java Mon Nov 06 17:35:40 2017 -0500
+++ b/src/java.base/share/classes/java/lang/System.java Mon Nov 06 17:48:00 2017 -0800
@@ -1799,7 +1799,8 @@
* is invoked rather than attempting to load a dynamic library.
* A filename matching the argument does not have to exist in the
* file system.
- * See the JNI Specification for more details.
+ * See the <a href="{@docRoot}/../specs/jni/index.html"> JNI Specification</a>
+ * for more details.
*
* Otherwise, the filename argument is mapped to a native library image in
* an implementation-dependent manner.
@@ -1835,7 +1836,8 @@
* specific prefix, file extension or path. If a native library
* called <code>libname</code> is statically linked with the VM, then the
* JNI_OnLoad_<code>libname</code> function exported by the library is invoked.
- * See the JNI Specification for more details.
+ * See the <a href="{@docRoot}/../specs/jni/index.html"> JNI Specification</a>
+ * for more details.
*
* Otherwise, the libname argument is loaded from a system library
* location and mapped to a native library image in an implementation-
--- a/src/java.base/share/native/libjava/ClassLoader.c Mon Nov 06 17:35:40 2017 -0500
+++ b/src/java.base/share/native/libjava/ClassLoader.c Mon Nov 06 17:48:00 2017 -0800
@@ -260,7 +260,6 @@
static jfieldID handleID;
static jfieldID jniVersionID;
-static jfieldID loadedID;
static void *procHandle;
static jboolean initIDs(JNIEnv *env)
@@ -276,9 +275,6 @@
jniVersionID = (*env)->GetFieldID(env, this, "jniVersion", "I");
if (jniVersionID == 0)
return JNI_FALSE;
- loadedID = (*env)->GetFieldID(env, this, "loaded", "Z");
- if (loadedID == 0)
- return JNI_FALSE;
procHandle = getProcessHandle();
}
return JNI_TRUE;
@@ -335,30 +331,31 @@
/*
* Class: java_lang_ClassLoader_NativeLibrary
- * Method: load
- * Signature: (Ljava/lang/String;Z)V
+ * Method: load0
+ * Signature: (Ljava/lang/String;Z)Z
*/
-JNIEXPORT void JNICALL
-Java_java_lang_ClassLoader_00024NativeLibrary_load
+JNIEXPORT jboolean JNICALL
+Java_java_lang_ClassLoader_00024NativeLibrary_load0
(JNIEnv *env, jobject this, jstring name, jboolean isBuiltin)
{
const char *cname;
jint jniVersion;
jthrowable cause;
void * handle;
+ jboolean loaded = JNI_FALSE;
if (!initIDs(env))
- return;
+ return JNI_FALSE;
cname = JNU_GetStringPlatformChars(env, name, 0);
if (cname == 0)
- return;
+ return JNI_FALSE;
handle = isBuiltin ? procHandle : JVM_LoadLibrary(cname);
if (handle) {
JNI_OnLoad_t JNI_OnLoad;
JNI_OnLoad = (JNI_OnLoad_t)findJniFunction(env, handle,
- isBuiltin ? cname : NULL,
- JNI_TRUE);
+ isBuiltin ? cname : NULL,
+ JNI_TRUE);
if (JNI_OnLoad) {
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
@@ -400,20 +397,21 @@
goto done;
}
(*env)->SetLongField(env, this, handleID, ptr_to_jlong(handle));
- (*env)->SetBooleanField(env, this, loadedID, JNI_TRUE);
+ loaded = JNI_TRUE;
done:
JNU_ReleaseStringPlatformChars(env, name, cname);
+ return loaded;
}
/*
* Class: java_lang_ClassLoader_NativeLibrary
* Method: unload
- * Signature: (Z)V
+ * Signature: (Ljava/lang/String;ZJ)V
*/
JNIEXPORT void JNICALL
Java_java_lang_ClassLoader_00024NativeLibrary_unload
-(JNIEnv *env, jobject this, jstring name, jboolean isBuiltin)
+(JNIEnv *env, jclass cls, jstring name, jboolean isBuiltin, jlong address)
{
const char *onUnloadSymbols[] = JNI_ONUNLOAD_SYMBOLS;
void *handle;
@@ -426,10 +424,10 @@
if (cname == NULL) {
return;
}
- handle = jlong_to_ptr((*env)->GetLongField(env, this, handleID));
+ handle = jlong_to_ptr(address);
JNI_OnUnload = (JNI_OnUnload_t )findJniFunction(env, handle,
- isBuiltin ? cname : NULL,
- JNI_FALSE);
+ isBuiltin ? cname : NULL,
+ JNI_FALSE);
if (JNI_OnUnload) {
JavaVM *jvm;
(*env)->GetJavaVM(env, &jvm);
@@ -443,11 +441,11 @@
/*
* Class: java_lang_ClassLoader_NativeLibrary
- * Method: find
+ * Method: findEntry
* Signature: (Ljava/lang/String;)J
*/
JNIEXPORT jlong JNICALL
-Java_java_lang_ClassLoader_00024NativeLibrary_find
+Java_java_lang_ClassLoader_00024NativeLibrary_findEntry
(JNIEnv *env, jobject this, jstring name)
{
jlong handle;
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/ClassLoader/nativeLibrary/NativeLibraryTest.java Mon Nov 06 17:48:00 2017 -0800
@@ -0,0 +1,129 @@
+/*
+ * Copyright (c) 2017, 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.
+ *
+ * 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.
+ */
+
+/*
+ * @test
+ * @bug 8164512
+ * @summary verify if the native library is unloaded when the class loader is GC'ed
+ * @build p.Test
+ * @run main/othervm/native -Xcheck:jni NativeLibraryTest
+ */
+
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+
+public class NativeLibraryTest {
+ static final Path CLASSES = Paths.get("classes");
+ static int unloadedCount = 0;
+
+ /*
+ * Called by JNI_OnUnload when the native library is unloaded
+ */
+ static void nativeLibraryUnloaded() {
+ unloadedCount++;
+ }
+
+ public static void main(String... args) throws Exception {
+ setup();
+
+ for (int count=1; count <= 5; count++) {
+ // create a class loader and load a native library
+ runTest();
+ // unloading the class loader and native library
+ System.gc();
+ // give Cleaner thread a chance to unload the native library
+ Thread.sleep(100);
+
+ // unloadedCount is incremented when the native library is unloaded
+ if (count != unloadedCount) {
+ throw new RuntimeException("Expected unloaded=" + count +
+ " but got=" + unloadedCount);
+ }
+ }
+ }
+
+ /*
+ * Loads p.Test class with a new class loader and its static initializer
+ * will load a native library.
+ *
+ * The class loader becomes unreachable when this method returns and
+ * the native library should be unloaded at some point after the class
+ * loader is garbage collected.
+ */
+ static void runTest() throws Exception {
+ // invoke p.Test.run() that loads the native library
+ Runnable r = newTestRunnable();
+ r.run();
+
+ // reload the native library by the same class loader
+ r.run();
+
+ // load the native library by another class loader
+ Runnable r1 = newTestRunnable();
+ try {
+ r1.run();
+ throw new RuntimeException("should fail to load the native library" +
+ " by another class loader");
+ } catch (UnsatisfiedLinkError e) {}
+ }
+
+ /*
+ * Loads p.Test class with a new class loader and returns
+ * a Runnable instance.
+ */
+ static Runnable newTestRunnable() throws Exception {
+ TestLoader loader = new TestLoader();
+ Class<?> c = Class.forName("p.Test", true, loader);
+ return (Runnable) c.newInstance();
+ }
+
+ static class TestLoader extends URLClassLoader {
+ static URL[] toURLs() {
+ try {
+ return new URL[] { CLASSES.toUri().toURL() };
+ } catch (MalformedURLException e) {
+ throw new Error(e);
+ }
+ }
+
+ TestLoader() {
+ super("testloader", toURLs(), ClassLoader.getSystemClassLoader());
+ }
+ }
+
+ /*
+ * move p/Test.class out from classpath to the scratch directory
+ */
+ static void setup() throws IOException {
+ String dir = System.getProperty("test.classes", ".");
+ Path file = Paths.get("p", "Test.class");
+ Files.createDirectories(CLASSES.resolve("p"));
+ Files.move(Paths.get(dir).resolve(file),
+ CLASSES.resolve("p").resolve("Test.class"));
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/ClassLoader/nativeLibrary/libnativeLibraryTest.c Mon Nov 06 17:48:00 2017 -0800
@@ -0,0 +1,74 @@
+/*
+ * Copyright (c) 2017, 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.
+ *
+ * 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.
+ */
+
+#include <stdio.h>
+#include <stdlib.h>
+
+#include "jni.h"
+#include "jni_util.h"
+
+static jint count = 0;
+static jclass test_class;
+static jint current_jni_version = JNI_VERSION_10;
+
+JNIEXPORT jint JNICALL
+JNI_OnLoad(JavaVM *vm, void *reserved) {
+ JNIEnv *env;
+ jclass cl;
+
+ (*vm)->GetEnv(vm, (void **) &env, current_jni_version);
+
+ cl = (*env)->FindClass(env, "NativeLibraryTest");
+ test_class = (*env)->NewGlobalRef(env, cl);
+
+ // increment the count when JNI_OnLoad is called
+ count++;
+
+ return current_jni_version;
+}
+
+JNIEXPORT void JNICALL
+JNI_OnUnload(JavaVM *vm, void *reserved) {
+ JNIEnv *env;
+ jmethodID mid;
+ jclass cl;
+
+ (*vm)->GetEnv(vm, (void **) &env, current_jni_version);
+ mid = (*env)->GetStaticMethodID(env, test_class, "nativeLibraryUnloaded", "()V");
+ (*env)->CallStaticVoidMethod(env, test_class, mid);
+ if ((*env)->ExceptionCheck(env)) {
+ (*env)->ExceptionDescribe(env);
+ (*env)->FatalError(env, "Exception thrown");
+ }
+
+ cl = (*env)->FindClass(env, "p/Test");
+ if (cl != NULL) {
+ (*env)->FatalError(env, "p/Test class should not be found");
+ }
+}
+
+JNIEXPORT jint JNICALL
+Java_p_Test_count
+(JNIEnv *env, jclass cls) {
+ return count;
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/test/jdk/java/lang/ClassLoader/nativeLibrary/p/Test.java Mon Nov 06 17:48:00 2017 -0800
@@ -0,0 +1,37 @@
+/*
+ * Copyright (c) 2017, 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.
+ *
+ * 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 p;
+
+public class Test implements Runnable {
+ public static native int count();
+
+ /**
+ * Tests if the native library is loaded.
+ */
+ public void run() {
+ System.loadLibrary("nativeLibraryTest");
+ if (count() != 1) {
+ throw new RuntimeException("Expected count = 1 but got " + count());
+ }
+ }
+}