diff --git a/src/java.base/aix/native/libnio/MappedMemoryUtils.c b/src/java.base/aix/native/libnio/MappedMemoryUtils.c index 5d0216cc251..e17ca2092af 100644 --- a/src/java.base/aix/native/libnio/MappedMemoryUtils.c +++ b/src/java.base/aix/native/libnio/MappedMemoryUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2024, 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 @@ -46,9 +46,8 @@ static long calculate_number_of_pages_in_range(void* address, size_t len, size_t return numPages; } -JNIEXPORT jboolean JNICALL -Java_java_nio_MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong address, - jlong len, jlong numPages) +jboolean JNICALL MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong address, + jlong len, jlong numPages) { jboolean loaded = JNI_TRUE; int result = 0; @@ -93,8 +92,7 @@ Java_java_nio_MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong addres } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_load0(JNIEnv *env, jobject obj, jlong address, +void JNICALL MappedMemoryUtils_load0(JNIEnv *env, jobject obj, jlong address, jlong len) { char *a = (char *)jlong_to_ptr(address); @@ -104,9 +102,8 @@ Java_java_nio_MappedMemoryUtils_load0(JNIEnv *env, jobject obj, jlong address, } } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_unload0(JNIEnv *env, jobject obj, jlong address, - jlong len) +void JNICALL MappedMemoryUtils_unload0(JNIEnv *env, jobject obj, jlong address, + jlong len) { char *a = (char *)jlong_to_ptr(address); int result = madvise((caddr_t)a, (size_t)len, MADV_DONTNEED); @@ -198,8 +195,7 @@ static int validate_msync_address(size_t address) return 0; } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, +void JNICALL MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, jlong address, jlong len) { void* a = (void *)jlong_to_ptr(address); @@ -218,3 +214,19 @@ Java_java_nio_MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, JNU_ThrowIOExceptionWithMessageAndLastError(env, "msync with parameter MS_SYNC failed"); } } + +#define FD "Ljava/io/FileDescriptor;" + +static JNINativeMethod methods[] = { + {"isLoaded0", "(JJJ)Z", (void *)&MappedMemoryUtils_isLoaded0}, + {"load0", "(JJ)V", (void *)&MappedMemoryUtils_load0}, + {"unload0", "(JJ)V", (void *)&MappedMemoryUtils_unload0}, + {"force0", "(" FD "JJ)V", (void *)&MappedMemoryUtils_force0}, +}; + +JNIEXPORT void JNICALL +Java_java_nio_MappedMemoryUtils_registerNatives(JNIEnv *env, jclass cls) +{ + (*env)->RegisterNatives(env, cls, + methods, sizeof(methods)/sizeof(methods[0])); +} diff --git a/src/java.base/share/classes/java/lang/ClassLoader.java b/src/java.base/share/classes/java/lang/ClassLoader.java index 5817c37d6f6..99056c353eb 100644 --- a/src/java.base/share/classes/java/lang/ClassLoader.java +++ b/src/java.base/share/classes/java/lang/ClassLoader.java @@ -2450,7 +2450,8 @@ public abstract class ClassLoader { * @param javaName the native method's declared name */ static long findNative(ClassLoader loader, Class clazz, String entryName, String javaName) { - long addr = findNativeInternal(loader, entryName); + NativeLibraries nativeLibraries = nativeLibrariesFor(loader); + long addr = nativeLibraries.find(entryName); if (addr != 0 && loader != null) { Reflection.ensureNativeAccess(clazz, clazz, javaName, true); } @@ -2462,11 +2463,11 @@ public abstract class ClassLoader { * to avoid a restricted check, as that check has already been performed when * obtaining the lookup. */ - static long findNativeInternal(ClassLoader loader, String entryName) { + static NativeLibraries nativeLibrariesFor(ClassLoader loader) { if (loader == null) { - return BootLoader.getNativeLibraries().find(entryName); + return BootLoader.getNativeLibraries(); } else { - return loader.libraries.find(entryName); + return loader.libraries; } } diff --git a/src/java.base/share/classes/java/lang/System.java b/src/java.base/share/classes/java/lang/System.java index 682b6ca2c2a..368db1928f1 100644 --- a/src/java.base/share/classes/java/lang/System.java +++ b/src/java.base/share/classes/java/lang/System.java @@ -70,6 +70,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Stream; import jdk.internal.javac.Restricted; +import jdk.internal.loader.NativeLibraries; import jdk.internal.logger.LoggerFinderLoader.TemporaryLoggerFinder; import jdk.internal.misc.Blocker; import jdk.internal.misc.CarrierThreadLocal; @@ -2662,8 +2663,8 @@ public final class System { } @Override - public long findNative(ClassLoader loader, String entry) { - return ClassLoader.findNativeInternal(loader, entry); + public NativeLibraries nativeLibrariesFor(ClassLoader loader) { + return ClassLoader.nativeLibrariesFor(loader); } @Override diff --git a/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java b/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java index c6e4443b570..d0dc60b3f77 100644 --- a/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java +++ b/src/java.base/share/classes/java/lang/foreign/SymbolLookup.java @@ -31,6 +31,7 @@ import jdk.internal.foreign.MemorySessionImpl; import jdk.internal.foreign.Utils; import jdk.internal.javac.Restricted; import jdk.internal.loader.BuiltinClassLoader; +import jdk.internal.loader.NativeLibraries; import jdk.internal.loader.NativeLibrary; import jdk.internal.loader.RawNativeLibraries; import jdk.internal.reflect.CallerSensitive; @@ -256,7 +257,8 @@ public interface SymbolLookup { if (Utils.containsNullChars(name)) return Optional.empty(); JavaLangAccess javaLangAccess = SharedSecrets.getJavaLangAccess(); // note: ClassLoader::findNative supports a null loader - long addr = javaLangAccess.findNative(loader, name); + NativeLibraries nativeLibraries = javaLangAccess.nativeLibrariesFor(loader); + long addr = nativeLibraries.find(name); return addr == 0L ? Optional.empty() : Optional.of(MemorySegment.ofAddress(addr) diff --git a/src/java.base/share/classes/java/nio/MappedMemoryUtils.java b/src/java.base/share/classes/java/nio/MappedMemoryUtils.java index cf6f953d8b2..241ac6b59e7 100644 --- a/src/java.base/share/classes/java/nio/MappedMemoryUtils.java +++ b/src/java.base/share/classes/java/nio/MappedMemoryUtils.java @@ -116,6 +116,18 @@ import jdk.internal.misc.Unsafe; private static native void unload0(long address, long length); private static native void force0(FileDescriptor fd, long address, long length) throws IOException; + /* Register the natives via the static initializer. + * + * This is required, as these native methods are "scoped methods" (see ScopedMemoryAccess). + * As such, it's better not to end up doing a full JNI lookup while in a scoped method context, + * as that will make the stack trace too deep. + */ + private static native void registerNatives(); + static { + registerNatives(); + isLoaded0(0, 0, 0); + } + // utility methods // Returns the distance (in bytes) of the buffer start from the diff --git a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java index a980fcc9896..98238af3831 100644 --- a/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java +++ b/src/java.base/share/classes/jdk/internal/access/JavaLangAccess.java @@ -29,6 +29,7 @@ import java.io.InputStream; import java.io.PrintStream; import java.lang.annotation.Annotation; import java.lang.foreign.MemorySegment; +import java.lang.foreign.SymbolLookup; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodType; import java.lang.module.ModuleDescriptor; @@ -46,6 +47,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.RejectedExecutionException; import java.util.stream.Stream; +import jdk.internal.loader.NativeLibraries; import jdk.internal.misc.CarrierThreadLocal; import jdk.internal.module.ServicesCatalog; import jdk.internal.reflect.ConstantPool; @@ -478,7 +480,11 @@ public interface JavaLangAccess { int getCharsUTF16(long i, int index, byte[] buf); - long findNative(ClassLoader loader, String entry); + /** + * Returns the {@link NativeLibraries} object associated with the provided class loader. + * This is used by {@link SymbolLookup#loaderLookup()}. + */ + NativeLibraries nativeLibrariesFor(ClassLoader loader); /** * Direct access to Shutdown.exit to avoid security manager checks diff --git a/src/java.base/unix/native/libnio/MappedMemoryUtils.c b/src/java.base/unix/native/libnio/MappedMemoryUtils.c index cdd8edff22a..6cfa3b47f84 100644 --- a/src/java.base/unix/native/libnio/MappedMemoryUtils.c +++ b/src/java.base/unix/native/libnio/MappedMemoryUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2023, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2024, 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 @@ -40,9 +40,8 @@ typedef unsigned char mincore_vec_t; typedef char mincore_vec_t; #endif -JNIEXPORT jboolean JNICALL -Java_java_nio_MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong address, - jlong len, jlong numPages) +jboolean JNICALL MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong address, + jlong len, jlong numPages) { jboolean loaded = JNI_TRUE; int result = 0; @@ -80,8 +79,7 @@ Java_java_nio_MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong addres } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_load0(JNIEnv *env, jobject obj, jlong address, +void JNICALL MappedMemoryUtils_load0(JNIEnv *env, jobject obj, jlong address, jlong len) { char *a = (char *)jlong_to_ptr(address); @@ -91,9 +89,8 @@ Java_java_nio_MappedMemoryUtils_load0(JNIEnv *env, jobject obj, jlong address, } } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_unload0(JNIEnv *env, jobject obj, jlong address, - jlong len) +void JNICALL MappedMemoryUtils_unload0(JNIEnv *env, jobject obj, jlong address, + jlong len) { char *a = (char *)jlong_to_ptr(address); int result = madvise((caddr_t)a, (size_t)len, MADV_DONTNEED); @@ -102,8 +99,7 @@ Java_java_nio_MappedMemoryUtils_unload0(JNIEnv *env, jobject obj, jlong address, } } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, +void JNICALL MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, jlong address, jlong len) { void* a = (void *)jlong_to_ptr(address); @@ -112,3 +108,19 @@ Java_java_nio_MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, JNU_ThrowIOExceptionWithMessageAndLastError(env, "msync with parameter MS_SYNC failed"); } } + +#define FD "Ljava/io/FileDescriptor;" + +static JNINativeMethod methods[] = { + {"isLoaded0", "(JJJ)Z", (void *)&MappedMemoryUtils_isLoaded0}, + {"load0", "(JJ)V", (void *)&MappedMemoryUtils_load0}, + {"unload0", "(JJ)V", (void *)&MappedMemoryUtils_unload0}, + {"force0", "(" FD "JJ)V", (void *)&MappedMemoryUtils_force0}, +}; + +JNIEXPORT void JNICALL +Java_java_nio_MappedMemoryUtils_registerNatives(JNIEnv *env, jclass cls) +{ + (*env)->RegisterNatives(env, cls, + methods, sizeof(methods)/sizeof(methods[0])); +} diff --git a/src/java.base/windows/native/libnio/MappedMemoryUtils.c b/src/java.base/windows/native/libnio/MappedMemoryUtils.c index 5017ca63a92..6e43dfb505f 100644 --- a/src/java.base/windows/native/libnio/MappedMemoryUtils.c +++ b/src/java.base/windows/native/libnio/MappedMemoryUtils.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 2001, 2021, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 2001, 2024, 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 @@ -30,9 +30,8 @@ #include "java_nio_MappedMemoryUtils.h" #include -JNIEXPORT jboolean JNICALL -Java_java_nio_MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong address, - jlong len, jlong numPages) +jboolean JNICALL MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong address, + jlong len, jlong numPages) { jboolean loaded = JNI_FALSE; /* Information not available? @@ -43,22 +42,19 @@ Java_java_nio_MappedMemoryUtils_isLoaded0(JNIEnv *env, jobject obj, jlong addres return loaded; } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_load0(JNIEnv *env, jobject obj, jlong address, +void JNICALL MappedMemoryUtils_load0(JNIEnv *env, jobject obj, jlong address, jlong len) { // no madvise available } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_unload0(JNIEnv *env, jobject obj, jlong address, - jlong len) +void JNICALL MappedMemoryUtils_unload0(JNIEnv *env, jobject obj, jlong address, + jlong len) { // no madvise available } -JNIEXPORT void JNICALL -Java_java_nio_MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, +void JNICALL MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, jlong address, jlong len) { void *a = (void *) jlong_to_ptr(address); @@ -106,3 +102,19 @@ Java_java_nio_MappedMemoryUtils_force0(JNIEnv *env, jobject obj, jobject fdo, JNU_ThrowIOExceptionWithLastError(env, "Flush failed"); } } + +#define FD "Ljava/io/FileDescriptor;" + +static JNINativeMethod methods[] = { + {"isLoaded0", "(JJJ)Z", (void *)&MappedMemoryUtils_isLoaded0}, + {"load0", "(JJ)V", (void *)&MappedMemoryUtils_load0}, + {"unload0", "(JJ)V", (void *)&MappedMemoryUtils_unload0}, + {"force0", "(" FD "JJ)V", (void *)&MappedMemoryUtils_force0}, +}; + +JNIEXPORT void JNICALL +Java_java_nio_MappedMemoryUtils_registerNatives(JNIEnv *env, jclass cls) +{ + (*env)->RegisterNatives(env, cls, + methods, sizeof(methods)/sizeof(methods[0])); +} diff --git a/test/jdk/java/foreign/TestMappedHandshake.java b/test/jdk/java/foreign/TestMappedHandshake.java new file mode 100644 index 00000000000..46fb4fb45fb --- /dev/null +++ b/test/jdk/java/foreign/TestMappedHandshake.java @@ -0,0 +1,142 @@ +/* + * Copyright (c) 2024, 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 + * @requires vm.flavor != "zero" + * @modules java.base/jdk.internal.vm.annotation java.base/jdk.internal.misc + * @key randomness + * @run testng/othervm TestMappedHandshake + * @run testng/othervm -Xint TestMappedHandshake + * @run testng/othervm -XX:TieredStopAtLevel=1 TestMappedHandshake + * @run testng/othervm -XX:-TieredCompilation TestMappedHandshake + */ + +import java.io.File; +import java.io.IOException; +import java.lang.foreign.Arena; +import java.lang.foreign.MemorySegment; +import java.nio.channels.FileChannel; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.StandardOpenOption; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import org.testng.annotations.Test; + +import static org.testng.Assert.*; + +public class TestMappedHandshake { + + static final int SEGMENT_SIZE = 1_000_000; + static final int ACCESS_START_DELAY_MILLIS = 100; + static final int POST_ACCESS_DELAY_MILLIS = 1; + static final int TIMED_RUN_TIME_SECONDS = 10; + static final int MAX_EXECUTOR_WAIT_SECONDS = 20; + + static final int NUM_ACCESSORS = 5; + + static final Path tempPath; + + static { + try { + File file = File.createTempFile("buffer", "txt"); + file.deleteOnExit(); + tempPath = file.toPath(); + Files.write(file.toPath(), new byte[SEGMENT_SIZE], StandardOpenOption.WRITE); + + } catch (IOException ex) { + throw new ExceptionInInitializerError(ex); + } + } + + @Test + public void testHandshake() throws InterruptedException, IOException { + try (FileChannel fileChannel = FileChannel.open(tempPath, StandardOpenOption.READ, StandardOpenOption.WRITE) ; + Arena arena = Arena.ofShared()) { + MemorySegment segment = fileChannel.map(FileChannel.MapMode.READ_WRITE, 0L, SEGMENT_SIZE, arena); + ExecutorService accessExecutor = Executors.newFixedThreadPool(NUM_ACCESSORS + 1); + // start handshaker + accessExecutor.execute(new Handshaker()); + Thread.sleep(ACCESS_START_DELAY_MILLIS); + // start accessors + for (int i = 0 ; i < NUM_ACCESSORS ; i++) { + accessExecutor.execute(new MappedSegmentAccessor(segment)); + } + + accessExecutor.shutdown(); + assertTrue(accessExecutor.awaitTermination(MAX_EXECUTOR_WAIT_SECONDS, TimeUnit.SECONDS)); + } + } + + static abstract class TimedAction implements Runnable { + @Override + public void run() { + long start = System.currentTimeMillis(); + while (true) { + try { + doAction(); + } catch (Throwable ex) { + // ignore + } finally { + long elapsed = System.currentTimeMillis() - start; + if (elapsed > TIMED_RUN_TIME_SECONDS * 1000) { + break; + } + } + } + } + + abstract void doAction() throws Throwable; + } + + static class MappedSegmentAccessor extends TimedAction { + + final MemorySegment segment; + + MappedSegmentAccessor(MemorySegment segment) { + this.segment = segment; + } + + @Override + void doAction() throws Throwable { + segment.load(); + Thread.sleep(POST_ACCESS_DELAY_MILLIS); + segment.isLoaded(); + Thread.sleep(POST_ACCESS_DELAY_MILLIS); + segment.unload(); + Thread.sleep(POST_ACCESS_DELAY_MILLIS); + segment.force(); + } + } + + static class Handshaker extends TimedAction { + + @Override + public void doAction() { + Arena.ofShared().close(); + } + } +}