1220 lines
46 KiB
Diff
1220 lines
46 KiB
Diff
|
|
---
|
||
|
|
.../jbooster/client/clientDataManager.cpp | 15 +
|
||
|
|
src/hotspot/share/runtime/arguments.cpp | 52 ++
|
||
|
|
src/hotspot/share/runtime/arguments.hpp | 1 +
|
||
|
|
.../java/net/ClassLoaderResourceCache.java | 542 ++++++++++++++++++
|
||
|
|
.../classes/java/net/URLClassLoader.java | 58 ++
|
||
|
|
.../jdk/internal/loader/URLClassPath.java | 58 ++
|
||
|
|
.../ClassLoaderResourceCacheTest.java | 344 +++++++++++
|
||
|
|
7 files changed, 1070 insertions(+)
|
||
|
|
create mode 100644 src/java.base/share/classes/java/net/ClassLoaderResourceCache.java
|
||
|
|
create mode 100644 test/jdk/java/net/URLClassLoader/ClassLoaderResourceCacheTest.java
|
||
|
|
|
||
|
|
diff --git a/src/hotspot/share/jbooster/client/clientDataManager.cpp b/src/hotspot/share/jbooster/client/clientDataManager.cpp
|
||
|
|
index 55b7d2a82..93fa45d7c 100644
|
||
|
|
--- a/src/hotspot/share/jbooster/client/clientDataManager.cpp
|
||
|
|
+++ b/src/hotspot/share/jbooster/client/clientDataManager.cpp
|
||
|
|
@@ -135,6 +135,21 @@ void ClientDataManager::init_client_duty_under_local_mode() {
|
||
|
|
|
||
|
|
jint ClientDataManager::init_clr_options() {
|
||
|
|
if (!is_clr_allowed()) return JNI_OK;
|
||
|
|
+
|
||
|
|
+ if (FLAG_SET_CMDLINE(UseClassLoaderResourceCache, true) != JVMFlag::SUCCESS) {
|
||
|
|
+ return JNI_EINVAL;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if (is_clr_being_used()) {
|
||
|
|
+ if (FLAG_SET_CMDLINE(LoadClassLoaderResourceCacheFile, cache_clr_path()) != JVMFlag::SUCCESS) {
|
||
|
|
+ return JNI_EINVAL;
|
||
|
|
+ }
|
||
|
|
+ } else if (is_server_available()) {
|
||
|
|
+ if (FLAG_SET_CMDLINE(DumpClassLoaderResourceCacheFile, cache_clr_path()) != JVMFlag::SUCCESS) {
|
||
|
|
+ return JNI_EINVAL;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
return JNI_OK;
|
||
|
|
}
|
||
|
|
|
||
|
|
diff --git a/src/hotspot/share/runtime/arguments.cpp b/src/hotspot/share/runtime/arguments.cpp
|
||
|
|
index 6a432ed6b..2921f3f38 100644
|
||
|
|
--- a/src/hotspot/share/runtime/arguments.cpp
|
||
|
|
+++ b/src/hotspot/share/runtime/arguments.cpp
|
||
|
|
@@ -4029,6 +4029,8 @@ jint Arguments::apply_ergo() {
|
||
|
|
result = JBoosterManager::init_phase1();
|
||
|
|
if (result != JNI_OK) return result;
|
||
|
|
}
|
||
|
|
+
|
||
|
|
+ init_class_loader_resource_cache_properties();
|
||
|
|
#endif // INCLUDE_JBOOSTER
|
||
|
|
|
||
|
|
result = set_shared_spaces_flags_and_archive_paths();
|
||
|
|
@@ -4358,6 +4360,56 @@ bool Arguments::copy_expand_pid(const char* src, size_t srclen,
|
||
|
|
}
|
||
|
|
|
||
|
|
#if INCLUDE_JBOOSTER
|
||
|
|
+
|
||
|
|
+jint Arguments::init_class_loader_resource_cache_properties() {
|
||
|
|
+ if (UseClassLoaderResourceCache == false) {
|
||
|
|
+ if (LoadClassLoaderResourceCacheFile != NULL || DumpClassLoaderResourceCacheFile != NULL) {
|
||
|
|
+ vm_exit_during_initialization("Set -XX:+UseClassLoaderResourceCache first");
|
||
|
|
+ }
|
||
|
|
+ return JNI_OK;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if (!add_property("jdk.jbooster.clrcache.enable=true", UnwriteableProperty, InternalProperty)) {
|
||
|
|
+ return JNI_ENOMEM;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ const int buf_len = 4096;
|
||
|
|
+ char buffer[buf_len];
|
||
|
|
+
|
||
|
|
+ if (LoadClassLoaderResourceCacheFile != NULL) {
|
||
|
|
+ if (jio_snprintf(buffer, buf_len, "jdk.jbooster.clrcache.load=%s", LoadClassLoaderResourceCacheFile) < 0) {
|
||
|
|
+ return JNI_ENOMEM;
|
||
|
|
+ }
|
||
|
|
+ if (!add_property(buffer, UnwriteableProperty, InternalProperty)) {
|
||
|
|
+ return JNI_ENOMEM;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if (DumpClassLoaderResourceCacheFile != NULL) {
|
||
|
|
+ if (jio_snprintf(buffer, buf_len, "jdk.jbooster.clrcache.dump=%s", DumpClassLoaderResourceCacheFile) < 0) {
|
||
|
|
+ return JNI_ENOMEM;
|
||
|
|
+ }
|
||
|
|
+ if (!add_property(buffer, UnwriteableProperty, InternalProperty)) {
|
||
|
|
+ return JNI_ENOMEM;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if (jio_snprintf(buffer, buf_len, "jdk.jbooster.clrcache.size=%u", ClassLoaderResourceCacheSizeEachLoader) < 0) {
|
||
|
|
+ return JNI_ENOMEM;
|
||
|
|
+ }
|
||
|
|
+ if (!add_property(buffer, UnwriteableProperty, InternalProperty)) {
|
||
|
|
+ return JNI_ENOMEM;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if (ClassLoaderResourceCacheVerboseMode) {
|
||
|
|
+ if (!add_property("jdk.jbooster.clrcache.verbose=true", UnwriteableProperty, InternalProperty)) {
|
||
|
|
+ return JNI_ENOMEM;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ return JNI_OK;
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
jint Arguments::init_jbooster_startup_signal_properties(const char* klass_name,
|
||
|
|
const char* method_name,
|
||
|
|
const char* method_signature) {
|
||
|
|
diff --git a/src/hotspot/share/runtime/arguments.hpp b/src/hotspot/share/runtime/arguments.hpp
|
||
|
|
index a66cc0f4d..cb2a04a2d 100644
|
||
|
|
--- a/src/hotspot/share/runtime/arguments.hpp
|
||
|
|
+++ b/src/hotspot/share/runtime/arguments.hpp
|
||
|
|
@@ -642,6 +642,7 @@ class Arguments : AllStatic {
|
||
|
|
}
|
||
|
|
|
||
|
|
#if INCLUDE_JBOOSTER
|
||
|
|
+ static jint init_class_loader_resource_cache_properties();
|
||
|
|
static jint init_jbooster_startup_signal_properties(const char* klass_name,
|
||
|
|
const char* method_name,
|
||
|
|
const char* method_signature);
|
||
|
|
diff --git a/src/java.base/share/classes/java/net/ClassLoaderResourceCache.java b/src/java.base/share/classes/java/net/ClassLoaderResourceCache.java
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..77ee4000e
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/src/java.base/share/classes/java/net/ClassLoaderResourceCache.java
|
||
|
|
@@ -0,0 +1,542 @@
|
||
|
|
+/*
|
||
|
|
+ * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. 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 java.net;
|
||
|
|
+
|
||
|
|
+import jdk.internal.loader.URLClassPath;
|
||
|
|
+
|
||
|
|
+import java.io.BufferedReader;
|
||
|
|
+import java.io.File;
|
||
|
|
+import java.io.FileNotFoundException;
|
||
|
|
+import java.io.FileReader;
|
||
|
|
+import java.io.IOException;
|
||
|
|
+import java.io.PrintWriter;
|
||
|
|
+import java.lang.invoke.MethodHandle;
|
||
|
|
+import java.lang.invoke.MethodHandles;
|
||
|
|
+import java.lang.reflect.Method;
|
||
|
|
+import java.nio.file.AtomicMoveNotSupportedException;
|
||
|
|
+import java.nio.file.FileAlreadyExistsException;
|
||
|
|
+import java.nio.file.Files;
|
||
|
|
+import java.nio.file.StandardCopyOption;
|
||
|
|
+import java.nio.file.attribute.PosixFilePermission;
|
||
|
|
+import java.security.AccessController;
|
||
|
|
+import java.security.PrivilegedAction;
|
||
|
|
+import java.util.ArrayList;
|
||
|
|
+import java.util.Collections;
|
||
|
|
+import java.util.HashMap;
|
||
|
|
+import java.util.HashSet;
|
||
|
|
+import java.util.LinkedHashMap;
|
||
|
|
+import java.util.List;
|
||
|
|
+import java.util.Map;
|
||
|
|
+import java.util.Objects;
|
||
|
|
+import java.util.Set;
|
||
|
|
+
|
||
|
|
+import sun.security.action.GetBooleanAction;
|
||
|
|
+import sun.security.action.GetIntegerAction;
|
||
|
|
+import sun.security.action.GetPropertyAction;
|
||
|
|
+
|
||
|
|
+/**
|
||
|
|
+ * We cache the mapping of "resource name -> resource url" to
|
||
|
|
+ * accelerate resource finding.
|
||
|
|
+ * Used only by {@link java.net.URLClassLoader}
|
||
|
|
+ */
|
||
|
|
+final class ClassLoaderResourceCache {
|
||
|
|
+ public static final int NO_IDX = -1;
|
||
|
|
+ public static final String NULL_OBJ = "<null>";
|
||
|
|
+
|
||
|
|
+ private static final boolean ENABLE_CACHE = GetBooleanAction.privilegedGetProperty("jdk.jbooster.clrcache.enable");
|
||
|
|
+ private static final String DUMP_CACHE_FILE = GetPropertyAction.privilegedGetProperty("jdk.jbooster.clrcache.dump");
|
||
|
|
+ private static final String LOAD_CACHE_FILE = GetPropertyAction.privilegedGetProperty("jdk.jbooster.clrcache.load");
|
||
|
|
+ private static final int MAX_CACHE_SIZE = GetIntegerAction.privilegedGetProperty("jdk.jbooster.clrcache.size", 2000);
|
||
|
|
+ private static final boolean VERBOSE_CACHE_FILE = GetBooleanAction.privilegedGetProperty("jdk.jbooster.clrcache.verbose");
|
||
|
|
+
|
||
|
|
+ private static final List<ClassLoaderResourceCache> cachesToDump;
|
||
|
|
+ private static final Map<ClassLoaderKey, LoadedCacheData> cachesToLoad;
|
||
|
|
+
|
||
|
|
+ static {
|
||
|
|
+ if ((DUMP_CACHE_FILE != null || LOAD_CACHE_FILE != null) && !ENABLE_CACHE) {
|
||
|
|
+ System.err.println("Please set loader.cache.enable to true!");
|
||
|
|
+ System.exit(1);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if (DUMP_CACHE_FILE != null) {
|
||
|
|
+ cachesToDump = Collections.synchronizedList(new ArrayList<>());
|
||
|
|
+ registerDumpShutdownHookPrivileged();
|
||
|
|
+ } else {
|
||
|
|
+ cachesToDump = null;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ if (LOAD_CACHE_FILE != null) {
|
||
|
|
+ // This map will never be modified after its initialization.
|
||
|
|
+ // So it doesn't have to be thread-safe.
|
||
|
|
+ cachesToLoad = new HashMap<>();
|
||
|
|
+ loadPrivileged(LOAD_CACHE_FILE);
|
||
|
|
+ } else {
|
||
|
|
+ cachesToLoad = null;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static boolean isEnabled() {
|
||
|
|
+ return ENABLE_CACHE;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static ClassLoaderResourceCache createIfEnabled(URLClassLoader holder, URL[] urls) {
|
||
|
|
+ return isEnabled() ? new ClassLoaderResourceCache(holder, urls) : null;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static URL findResource(URLClassPath ucp, String name, boolean check,
|
||
|
|
+ String cachedURLString, int cachedIndex,
|
||
|
|
+ int[] resIndex) {
|
||
|
|
+ return URLClassPathUtil.findResource(ucp, name, check, cachedURLString, cachedIndex, resIndex);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static URL findResource(URLClassPath ucp, String name, boolean check, int[] resIndex) {
|
||
|
|
+ return URLClassPathUtil.findResource(ucp, name, check, resIndex);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ @SuppressWarnings("removal")
|
||
|
|
+ private static void registerDumpShutdownHookPrivileged() {
|
||
|
|
+ AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
||
|
|
+ Runtime.getRuntime().addShutdownHook(new Thread(() -> {
|
||
|
|
+ AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
||
|
|
+ dump(DUMP_CACHE_FILE);
|
||
|
|
+ return null;
|
||
|
|
+ });
|
||
|
|
+ }));
|
||
|
|
+ return null;
|
||
|
|
+ });
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ @SuppressWarnings("removal")
|
||
|
|
+ private static void loadPrivileged(String filePath) {
|
||
|
|
+ AccessController.doPrivileged((PrivilegedAction<Void>) () -> {
|
||
|
|
+ load(filePath);
|
||
|
|
+ return null;
|
||
|
|
+ });
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Dump all class loader resource caches to a file.
|
||
|
|
+ *
|
||
|
|
+ * @param filePath The file to store the cache
|
||
|
|
+ */
|
||
|
|
+ private static void dump(String filePath) {
|
||
|
|
+ File file = new File(filePath);
|
||
|
|
+ // Do not re-dump if the cache file already exists.
|
||
|
|
+ if (file.isFile()) {
|
||
|
|
+ return;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ // Treat the tmp file as a file lock (see JBoosterManager::calc_tmp_cache_path()).
|
||
|
|
+ String tmpFilePath = filePath.concat(".tmp");
|
||
|
|
+ File tmpFile = new File(tmpFilePath);
|
||
|
|
+ try {
|
||
|
|
+ // Create the tmp file. Skip dump if the tmp file already exists
|
||
|
|
+ // (meaning someone else is dumping) or fails to be created.
|
||
|
|
+ if (!tmpFile.createNewFile()) {
|
||
|
|
+ return;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ // Double check if the cache file already exists.
|
||
|
|
+ if (file.isFile()) {
|
||
|
|
+ // Release the tmp file lock.
|
||
|
|
+ tmpFile.delete();
|
||
|
|
+ return;
|
||
|
|
+ }
|
||
|
|
+ } catch (IOException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ return;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ try (PrintWriter writer = new PrintWriter(tmpFile)) {
|
||
|
|
+ // synchronized to avoid ConcurrentModificationException
|
||
|
|
+ synchronized (cachesToDump) {
|
||
|
|
+ for (ClassLoaderResourceCache cache : cachesToDump) {
|
||
|
|
+ writeCache(writer, cache);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ } catch (FileNotFoundException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ return;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ boolean renameSuccessful = false;
|
||
|
|
+ try {
|
||
|
|
+ // Do not rename if the target file already exists.
|
||
|
|
+ // Theoretically, the target file cannot exist.
|
||
|
|
+ if (!file.isFile()) {
|
||
|
|
+ Set<PosixFilePermission> perms = new HashSet<PosixFilePermission>();
|
||
|
|
+ perms.add(PosixFilePermission.OWNER_READ);
|
||
|
|
+ Files.setPosixFilePermissions(tmpFile.toPath(), perms);
|
||
|
|
+ Files.move(tmpFile.toPath(), file.toPath(), StandardCopyOption.ATOMIC_MOVE);
|
||
|
|
+ renameSuccessful = true;
|
||
|
|
+ }
|
||
|
|
+ } catch (AtomicMoveNotSupportedException e) {
|
||
|
|
+ System.err.println("The file system does not support atomic move in the same dir?");
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ } catch (FileAlreadyExistsException e) {
|
||
|
|
+ System.err.println("The file already exists? Should be a bug.");
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ } catch (IOException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ } finally {
|
||
|
|
+ if (!renameSuccessful) {
|
||
|
|
+ // Release the tmp file lock if the renaming fails.
|
||
|
|
+ tmpFile.delete();
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ private static void writeCache(PrintWriter writer, ClassLoaderResourceCache cache) {
|
||
|
|
+ String holderClassName = cache.holderClassName;
|
||
|
|
+ String holderName = cache.holderName;
|
||
|
|
+ writer.println("L|" + holderClassName
|
||
|
|
+ + "|" + (holderName == null ? NULL_OBJ : holderName)
|
||
|
|
+ + "|" + cache.originalURLsHash);
|
||
|
|
+ synchronized (cache.resourceUrlCache) {
|
||
|
|
+ for (Map.Entry<String, ResourceCacheEntry> e : cache.resourceUrlCache.entrySet()) {
|
||
|
|
+ String resourceName = e.getKey();
|
||
|
|
+ ResourceCacheEntry entry = e.getValue();
|
||
|
|
+ if (entry.isFound()) {
|
||
|
|
+ writer.println("E|" + resourceName + "|" + entry.getIndex()
|
||
|
|
+ + (VERBOSE_CACHE_FILE ? ("|" + entry.getURL().toExternalForm()) : ""));
|
||
|
|
+ } else {
|
||
|
|
+ writer.println("E|" + resourceName + "|" + NO_IDX + (VERBOSE_CACHE_FILE ? ("|" + NULL_OBJ) : ""));
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Load all class loader resource caches from a file.
|
||
|
|
+ *
|
||
|
|
+ * @param filePath The file that stores the cache
|
||
|
|
+ */
|
||
|
|
+ private static void load(String filePath) {
|
||
|
|
+ try (FileReader fr = new FileReader(filePath);
|
||
|
|
+ BufferedReader br = new BufferedReader(fr)) {
|
||
|
|
+ String line;
|
||
|
|
+ LoadedCacheData cacheData = null;
|
||
|
|
+ while ((line = br.readLine()) != null) {
|
||
|
|
+ if (line.startsWith("L|")) {
|
||
|
|
+ ClassLoaderKey key = readCacheLoader(line);
|
||
|
|
+ cacheData = new LoadedCacheData();
|
||
|
|
+ cachesToLoad.put(key, cacheData);
|
||
|
|
+ } else if (line.startsWith("E|")) {
|
||
|
|
+ readCacheEntry(line, cacheData);
|
||
|
|
+ } else {
|
||
|
|
+ System.err.println("Unknown line: " + line);
|
||
|
|
+ System.exit(1);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ } catch (IOException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ System.exit(1);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ private static ClassLoaderKey readCacheLoader(String line) {
|
||
|
|
+ String[] sp = line.split("\\|");
|
||
|
|
+ if (sp.length != 4) {
|
||
|
|
+ System.err.println("Unknown line: " + line);
|
||
|
|
+ System.exit(1);
|
||
|
|
+ }
|
||
|
|
+ return new ClassLoaderKey(sp[1], NULL_OBJ.equals(sp[2]) ? null : sp[2], Integer.parseInt(sp[3]));
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ private static void readCacheEntry(String line, LoadedCacheData cacheData) {
|
||
|
|
+ String[] sp = line.split("\\|", VERBOSE_CACHE_FILE ? 4 : 3);
|
||
|
|
+ if (sp.length != (VERBOSE_CACHE_FILE ? 4 : 3)) {
|
||
|
|
+ System.err.println("Unknown line: " + line);
|
||
|
|
+ System.exit(1);
|
||
|
|
+ }
|
||
|
|
+ int idx = Integer.parseInt(sp[2]);
|
||
|
|
+ if (idx == NO_IDX && (VERBOSE_CACHE_FILE ? NULL_OBJ.equals(sp[3]) : true)) {
|
||
|
|
+ cacheData.addLoadedResourceCacheEntry(sp[1], null, NO_IDX);
|
||
|
|
+ } else {
|
||
|
|
+ cacheData.addLoadedResourceCacheEntry(sp[1], (VERBOSE_CACHE_FILE ? sp[3] : null), idx);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ private static <K, V> Map<K, V> createCacheMap() {
|
||
|
|
+ return Collections.synchronizedMap(new LinkedHashMap<>(MAX_CACHE_SIZE, 0.75f, true) {
|
||
|
|
+ @Override
|
||
|
|
+ protected boolean removeEldestEntry(Map.Entry<K, V> eldest) {
|
||
|
|
+ return size() >= MAX_CACHE_SIZE;
|
||
|
|
+ }
|
||
|
|
+ });
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ private static int fastHashOfURLs(URL[] urls) {
|
||
|
|
+ if (urls.length == 0) return 0;
|
||
|
|
+ return urls.length ^ urls[0].hashCode();
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ private final String holderClassName;
|
||
|
|
+ private final String holderName;
|
||
|
|
+
|
||
|
|
+ private final int originalURLsHash;
|
||
|
|
+
|
||
|
|
+ private final Map<String, ResourceCacheEntry> resourceUrlCache;
|
||
|
|
+
|
||
|
|
+ // Stores the cached data loaded form the cache file. We use the
|
||
|
|
+ // index to find the url of the resource quickly. We didn't choose
|
||
|
|
+ // to generate the url from the url string and just put it into
|
||
|
|
+ // the resourceUrlCache because (1) creating a url costs much time;
|
||
|
|
+ // (2) we'd better check that our cache entry is correct.
|
||
|
|
+ private final Map<String, LoadedResourceCacheEntry> loadedResourceUrlCache;
|
||
|
|
+
|
||
|
|
+ private final Map<String, ClassNotFoundException> classNotFoundExceptionCache;
|
||
|
|
+
|
||
|
|
+ private ClassLoaderResourceCache(URLClassLoader holder, URL[] urls) {
|
||
|
|
+ this.holderClassName = holder.getClass().getName();
|
||
|
|
+ this.holderName = holder.getName();
|
||
|
|
+ this.originalURLsHash = fastHashOfURLs(urls);
|
||
|
|
+ this.resourceUrlCache = createCacheMap();
|
||
|
|
+ this.classNotFoundExceptionCache = createCacheMap();
|
||
|
|
+
|
||
|
|
+ Map<String, LoadedResourceCacheEntry> loadedMap = Collections.emptyMap();
|
||
|
|
+ if (cachesToLoad != null) {
|
||
|
|
+ ClassLoaderKey key = new ClassLoaderKey(holderClassName, holderName, originalURLsHash);
|
||
|
|
+ LoadedCacheData loadedCacheData = cachesToLoad.get(key);
|
||
|
|
+ if (loadedCacheData != null) {
|
||
|
|
+ loadedMap = loadedCacheData.getLoadedResourceUrlCache();
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ this.loadedResourceUrlCache = loadedMap;
|
||
|
|
+
|
||
|
|
+ if (cachesToDump != null) {
|
||
|
|
+ cachesToDump.add(this);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Throws the exception if cached.
|
||
|
|
+ *
|
||
|
|
+ * @param name the resource name
|
||
|
|
+ * @throws ClassNotFoundException if the exception is cached
|
||
|
|
+ */
|
||
|
|
+ public void fastClassNotFoundException(String name) throws ClassNotFoundException {
|
||
|
|
+ ClassNotFoundException classNotFoundException = classNotFoundExceptionCache.get(name);
|
||
|
|
+ if (classNotFoundException != null) {
|
||
|
|
+ throw classNotFoundException;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Puts the cache.
|
||
|
|
+ *
|
||
|
|
+ * @param name the resource name
|
||
|
|
+ * @param exception the value to cache
|
||
|
|
+ */
|
||
|
|
+ public void cacheClassNotFoundException(String name, ClassNotFoundException exception) {
|
||
|
|
+ classNotFoundExceptionCache.put(name, exception);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Gets the cache loaded from a file.
|
||
|
|
+ *
|
||
|
|
+ * @param name the resource name
|
||
|
|
+ * @return the cached value
|
||
|
|
+ */
|
||
|
|
+ public LoadedResourceCacheEntry getLoadedResourceCache(String name) {
|
||
|
|
+ return loadedResourceUrlCache.get(name);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Gets the cache.
|
||
|
|
+ *
|
||
|
|
+ * @param name the resource name
|
||
|
|
+ * @return the cached value
|
||
|
|
+ */
|
||
|
|
+ public ResourceCacheEntry getResourceCache(String name) {
|
||
|
|
+ return resourceUrlCache.get(name);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Puts the cache.
|
||
|
|
+ *
|
||
|
|
+ * @param name the resource name
|
||
|
|
+ * @param url the url to cache
|
||
|
|
+ * @param idx the index of url to cache
|
||
|
|
+ */
|
||
|
|
+ public void cacheResourceUrl(String name, URL url, int idx) {
|
||
|
|
+ resourceUrlCache.put(name, new ResourceCacheEntry(url, url == null ? NO_IDX : idx));
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * Clears the caches. No call site yet.
|
||
|
|
+ */
|
||
|
|
+ public void clearCache() {
|
||
|
|
+ classNotFoundExceptionCache.clear();
|
||
|
|
+ resourceUrlCache.clear();
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * The key to identify a class loader.
|
||
|
|
+ */
|
||
|
|
+ private static class ClassLoaderKey {
|
||
|
|
+ private final String className;
|
||
|
|
+ private final String name;
|
||
|
|
+ private final int urlsHash;
|
||
|
|
+
|
||
|
|
+ public ClassLoaderKey(String className, String name, int urlsHash) {
|
||
|
|
+ this.className = className;
|
||
|
|
+ this.name = name;
|
||
|
|
+ this.urlsHash = urlsHash;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ @Override
|
||
|
|
+ public boolean equals(Object o) {
|
||
|
|
+ if (this == o) {
|
||
|
|
+ return true;
|
||
|
|
+ }
|
||
|
|
+ if (o instanceof ClassLoaderKey that) {
|
||
|
|
+ return Objects.equals(className, that.className)
|
||
|
|
+ && Objects.equals(name, that.name)
|
||
|
|
+ && Objects.equals(urlsHash, that.urlsHash);
|
||
|
|
+ }
|
||
|
|
+ return false;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ @Override
|
||
|
|
+ public int hashCode() {
|
||
|
|
+ return Objects.hash(className, name, urlsHash);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * The cached resource info.
|
||
|
|
+ */
|
||
|
|
+ public static class ResourceCacheEntry {
|
||
|
|
+ private final URL url;
|
||
|
|
+ private final int index;
|
||
|
|
+
|
||
|
|
+ public ResourceCacheEntry(URL url, int index) {
|
||
|
|
+ this.url = url;
|
||
|
|
+ this.index = index;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public boolean isFound() {
|
||
|
|
+ return url != null;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public URL getURL() {
|
||
|
|
+ return url;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public int getIndex() {
|
||
|
|
+ return index;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static class LoadedResourceCacheEntry {
|
||
|
|
+ private final String urlString;
|
||
|
|
+ private final int index;
|
||
|
|
+
|
||
|
|
+ public LoadedResourceCacheEntry(String urlString, int index) {
|
||
|
|
+ this.urlString = urlString;
|
||
|
|
+ this.index = index;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public String getURLString() {
|
||
|
|
+ return urlString;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public int getIndex() {
|
||
|
|
+ return index;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ private static class LoadedCacheData {
|
||
|
|
+ private final Map<String, LoadedResourceCacheEntry> loadedResourceUrlCache;
|
||
|
|
+
|
||
|
|
+ public LoadedCacheData() {
|
||
|
|
+ this.loadedResourceUrlCache = new HashMap<>();
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public Map<String, LoadedResourceCacheEntry> getLoadedResourceUrlCache() {
|
||
|
|
+ return loadedResourceUrlCache;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public void addLoadedResourceCacheEntry(String name, String urlString, int index) {
|
||
|
|
+ loadedResourceUrlCache.put(name, new LoadedResourceCacheEntry(urlString, index));
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+/**
|
||
|
|
+ * We don't want to add new public methods in URLClassPath. So we add two
|
||
|
|
+ * private method (findResourceWithIndex) and use method handle to invoke
|
||
|
|
+ * them.
|
||
|
|
+ */
|
||
|
|
+@SuppressWarnings("removal")
|
||
|
|
+class URLClassPathUtil {
|
||
|
|
+ private static final MethodHandle resourceFinder1;
|
||
|
|
+ private static final MethodHandle resourceFinder2;
|
||
|
|
+
|
||
|
|
+ static {
|
||
|
|
+ MethodHandle mh1 = null;
|
||
|
|
+ MethodHandle mh2 = null;
|
||
|
|
+ try {
|
||
|
|
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||
|
|
+ Method m1 = URLClassPath.class.getDeclaredMethod("findResourceWithIndex", String.class, boolean.class, String.class, int.class, int[].class);
|
||
|
|
+ Method m2 = URLClassPath.class.getDeclaredMethod("findResourceWithIndex", String.class, boolean.class, int[].class);
|
||
|
|
+ AccessController.doPrivileged(
|
||
|
|
+ (PrivilegedAction<Void>) () -> {
|
||
|
|
+ m1.setAccessible(true);
|
||
|
|
+ m2.setAccessible(true);
|
||
|
|
+ return null;
|
||
|
|
+ });
|
||
|
|
+ mh1 = lookup.unreflect(m1);
|
||
|
|
+ mh2 = lookup.unreflect(m2);
|
||
|
|
+ } catch (NoSuchMethodException | IllegalAccessException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ System.exit(1);
|
||
|
|
+ }
|
||
|
|
+ resourceFinder1 = mh1;
|
||
|
|
+ resourceFinder2 = mh2;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static URL findResource(URLClassPath ucp, String name, boolean check,
|
||
|
|
+ String cachedURLString, int cachedIndex,
|
||
|
|
+ int[] resIndex) {
|
||
|
|
+ try {
|
||
|
|
+ return (URL) resourceFinder1.invoke(ucp, name, check, cachedURLString, cachedIndex, resIndex);
|
||
|
|
+ } catch (Throwable throwable) {
|
||
|
|
+ throwable.printStackTrace();
|
||
|
|
+ System.exit(1);
|
||
|
|
+ }
|
||
|
|
+ return null;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static URL findResource(URLClassPath ucp, String name, boolean check, int[] resIndex) {
|
||
|
|
+ try {
|
||
|
|
+ return (URL) resourceFinder2.invoke(ucp, name, check, resIndex);
|
||
|
|
+ } catch (Throwable throwable) {
|
||
|
|
+ throwable.printStackTrace();
|
||
|
|
+ System.exit(1);
|
||
|
|
+ }
|
||
|
|
+ return null;
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
diff --git a/src/java.base/share/classes/java/net/URLClassLoader.java b/src/java.base/share/classes/java/net/URLClassLoader.java
|
||
|
|
index 8314d5bb3..b3362d9f6 100644
|
||
|
|
--- a/src/java.base/share/classes/java/net/URLClassLoader.java
|
||
|
|
+++ b/src/java.base/share/classes/java/net/URLClassLoader.java
|
||
|
|
@@ -88,6 +88,8 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
@SuppressWarnings("removal")
|
||
|
|
private final AccessControlContext acc;
|
||
|
|
|
||
|
|
+ private final ClassLoaderResourceCache loaderCache;
|
||
|
|
+
|
||
|
|
/**
|
||
|
|
* Constructs a new URLClassLoader for the given URLs. The URLs will be
|
||
|
|
* searched in the order specified for classes and resources after first
|
||
|
|
@@ -115,6 +117,7 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
super(parent);
|
||
|
|
this.acc = AccessController.getContext();
|
||
|
|
this.ucp = new URLClassPath(urls, acc);
|
||
|
|
+ this.loaderCache = ClassLoaderResourceCache.createIfEnabled(this, urls);
|
||
|
|
}
|
||
|
|
|
||
|
|
URLClassLoader(String name, URL[] urls, ClassLoader parent,
|
||
|
|
@@ -122,6 +125,7 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
super(name, parent);
|
||
|
|
this.acc = acc;
|
||
|
|
this.ucp = new URLClassPath(urls, acc);
|
||
|
|
+ this.loaderCache = ClassLoaderResourceCache.createIfEnabled(this, urls);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
@@ -151,12 +155,14 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
super();
|
||
|
|
this.acc = AccessController.getContext();
|
||
|
|
this.ucp = new URLClassPath(urls, acc);
|
||
|
|
+ this.loaderCache = ClassLoaderResourceCache.createIfEnabled(this, urls);
|
||
|
|
}
|
||
|
|
|
||
|
|
URLClassLoader(URL[] urls, @SuppressWarnings("removal") AccessControlContext acc) {
|
||
|
|
super();
|
||
|
|
this.acc = acc;
|
||
|
|
this.ucp = new URLClassPath(urls, acc);
|
||
|
|
+ this.loaderCache = ClassLoaderResourceCache.createIfEnabled(this, urls);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
@@ -187,6 +193,7 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
super(parent);
|
||
|
|
this.acc = AccessController.getContext();
|
||
|
|
this.ucp = new URLClassPath(urls, factory, acc);
|
||
|
|
+ this.loaderCache = ClassLoaderResourceCache.createIfEnabled(this, urls);
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@@ -219,6 +226,7 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
super(name, parent);
|
||
|
|
this.acc = AccessController.getContext();
|
||
|
|
this.ucp = new URLClassPath(urls, acc);
|
||
|
|
+ this.loaderCache = ClassLoaderResourceCache.createIfEnabled(this, urls);
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
@@ -249,6 +257,7 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
super(name, parent);
|
||
|
|
this.acc = AccessController.getContext();
|
||
|
|
this.ucp = new URLClassPath(urls, factory, acc);
|
||
|
|
+ this.loaderCache = ClassLoaderResourceCache.createIfEnabled(this, urls);
|
||
|
|
}
|
||
|
|
|
||
|
|
/* A map (used as a set) to keep track of closeable local resources
|
||
|
|
@@ -401,6 +410,25 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
return ucp.getURLs();
|
||
|
|
}
|
||
|
|
|
||
|
|
+ /**
|
||
|
|
+ * This method is overrided by ClassLoaderResourceCache to quickly throw
|
||
|
|
+ * the ClassNotFoundException if it is cached.
|
||
|
|
+ */
|
||
|
|
+ protected Class<?> loadClass(String name, boolean resolve)
|
||
|
|
+ throws ClassNotFoundException
|
||
|
|
+ {
|
||
|
|
+ if (ClassLoaderResourceCache.isEnabled()) {
|
||
|
|
+ loaderCache.fastClassNotFoundException(name);
|
||
|
|
+ try {
|
||
|
|
+ return super.loadClass(name, resolve);
|
||
|
|
+ } catch (ClassNotFoundException ex) {
|
||
|
|
+ loaderCache.cacheClassNotFoundException(name, ex);
|
||
|
|
+ throw ex;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ return super.loadClass(name, resolve);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
/**
|
||
|
|
* Finds and loads the class with the specified name from the URL search
|
||
|
|
* path. Any URLs referring to JAR files are loaded and opened as needed
|
||
|
|
@@ -649,6 +677,36 @@ public class URLClassLoader extends SecureClassLoader implements Closeable {
|
||
|
|
* if the resource could not be found, or if the loader is closed.
|
||
|
|
*/
|
||
|
|
public URL findResource(final String name) {
|
||
|
|
+ if (ClassLoaderResourceCache.isEnabled()) {
|
||
|
|
+ ClassLoaderResourceCache.ResourceCacheEntry entry = loaderCache.getResourceCache(name);
|
||
|
|
+ if (entry != null) {
|
||
|
|
+ return entry.getURL();
|
||
|
|
+ }
|
||
|
|
+ ClassLoaderResourceCache.LoadedResourceCacheEntry loadedEntry
|
||
|
|
+ = loaderCache.getLoadedResourceCache(name);
|
||
|
|
+ int[] urlIndex = {-1};
|
||
|
|
+ @SuppressWarnings("removal")
|
||
|
|
+ URL url = AccessController.doPrivileged(
|
||
|
|
+ new PrivilegedAction<>() {
|
||
|
|
+ public URL run() {
|
||
|
|
+ if (loadedEntry == null) {
|
||
|
|
+ return ClassLoaderResourceCache.findResource(ucp, name, true, urlIndex);
|
||
|
|
+ } else if (loadedEntry.getIndex() == ClassLoaderResourceCache.NO_IDX) {
|
||
|
|
+ return null;
|
||
|
|
+ } else {
|
||
|
|
+ return ClassLoaderResourceCache.findResource(ucp, name, true,
|
||
|
|
+ loadedEntry.getURLString(), loadedEntry.getIndex(), urlIndex);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ }, acc);
|
||
|
|
+
|
||
|
|
+ if (url != null) {
|
||
|
|
+ url = URLClassPath.checkURL(url);
|
||
|
|
+ }
|
||
|
|
+ loaderCache.cacheResourceUrl(name, url, urlIndex[0]);
|
||
|
|
+ return url;
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
/*
|
||
|
|
* The same restriction to finding classes applies to resources
|
||
|
|
*/
|
||
|
|
diff --git a/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java b/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java
|
||
|
|
index 0cc500127..61864b5f1 100644
|
||
|
|
--- a/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java
|
||
|
|
+++ b/src/java.base/share/classes/jdk/internal/loader/URLClassPath.java
|
||
|
|
@@ -1294,4 +1294,62 @@ public class URLClassPath {
|
||
|
|
return null;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * This method is only for java.net.ClassLoaderResourceCache!
|
||
|
|
+ *
|
||
|
|
+ * Finds the resource (and its index in url array) with the
|
||
|
|
+ * specified name on the URL search path or null if not found
|
||
|
|
+ * or security check fails.
|
||
|
|
+ * Search the specific index first.
|
||
|
|
+ *
|
||
|
|
+ * @param name the name of the resource
|
||
|
|
+ * @param check whether to perform a security check
|
||
|
|
+ * @param cachedURLString the url of cachedIndex
|
||
|
|
+ * @param cachedIndex the index of cachedURLString
|
||
|
|
+ * @param resIndex the index of URL in loaders
|
||
|
|
+ * @return a {@code URL} for the resource, or {@code null}
|
||
|
|
+ * if the resource could not be found.
|
||
|
|
+ * @see java.net.URLClassPathUtil
|
||
|
|
+ */
|
||
|
|
+ private URL findResourceWithIndex(String name, boolean check,
|
||
|
|
+ String cachedURLString, int cachedIndex,
|
||
|
|
+ int[] resIndex) {
|
||
|
|
+ Loader loader = getLoader(cachedIndex);
|
||
|
|
+ if (loader != null) {
|
||
|
|
+ URL url = loader.findResource(name, check);
|
||
|
|
+ if (url != null && (cachedURLString == null || cachedURLString.equals(url.toExternalForm()))) {
|
||
|
|
+ resIndex[0] = cachedIndex;
|
||
|
|
+ return url;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ return findResourceWithIndex(name, check, resIndex);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ /**
|
||
|
|
+ * This method is only for java.net.ClassLoaderResourceCache!
|
||
|
|
+ *
|
||
|
|
+ * Finds the resource (and its index in url array) with the
|
||
|
|
+ * specified name on the URL search path or null if not found
|
||
|
|
+ * or security check fails.
|
||
|
|
+ *
|
||
|
|
+ * @param name the name of the resource
|
||
|
|
+ * @param check whether to perform a security check
|
||
|
|
+ * @param resIndex the index of URL in loaders
|
||
|
|
+ * @return a {@code URL} for the resource, or {@code null}
|
||
|
|
+ * if the resource could not be found.
|
||
|
|
+ * @see java.net.URLClassPathUtil
|
||
|
|
+ */
|
||
|
|
+ private URL findResourceWithIndex(String name, boolean check, int[] resIndex) {
|
||
|
|
+ Loader loader;
|
||
|
|
+ for (int i = 0; (loader = getLoader(i)) != null; i++) {
|
||
|
|
+ URL url = loader.findResource(name, check);
|
||
|
|
+ if (url != null) {
|
||
|
|
+ resIndex[0] = i;
|
||
|
|
+ return url;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ resIndex[0] = -1;
|
||
|
|
+ return null;
|
||
|
|
+ }
|
||
|
|
}
|
||
|
|
diff --git a/test/jdk/java/net/URLClassLoader/ClassLoaderResourceCacheTest.java b/test/jdk/java/net/URLClassLoader/ClassLoaderResourceCacheTest.java
|
||
|
|
new file mode 100644
|
||
|
|
index 000000000..550dc04be
|
||
|
|
--- /dev/null
|
||
|
|
+++ b/test/jdk/java/net/URLClassLoader/ClassLoaderResourceCacheTest.java
|
||
|
|
@@ -0,0 +1,344 @@
|
||
|
|
+/*
|
||
|
|
+ * Copyright (c) 2020, 2023, Huawei Technologies Co., Ltd. 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.
|
||
|
|
+ */
|
||
|
|
+
|
||
|
|
+import org.testng.Assert;
|
||
|
|
+import org.testng.annotations.BeforeMethod;
|
||
|
|
+import org.testng.annotations.Test;
|
||
|
|
+
|
||
|
|
+import java.io.File;
|
||
|
|
+import java.lang.invoke.MethodHandle;
|
||
|
|
+import java.lang.invoke.MethodHandles;
|
||
|
|
+import java.lang.reflect.Field;
|
||
|
|
+import java.lang.reflect.Method;
|
||
|
|
+import java.net.MalformedURLException;
|
||
|
|
+import java.net.URL;
|
||
|
|
+import java.net.URLClassLoader;
|
||
|
|
+import java.util.Arrays;
|
||
|
|
+import java.util.Comparator;
|
||
|
|
+import java.util.Map;
|
||
|
|
+import java.util.Objects;
|
||
|
|
+
|
||
|
|
+/*
|
||
|
|
+ * @test
|
||
|
|
+ * @run testng/othervm
|
||
|
|
+ * --add-opens=java.base/java.net=ALL-UNNAMED
|
||
|
|
+ * --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED
|
||
|
|
+ * -XX:+UnlockExperimentalVMOptions
|
||
|
|
+ * -XX:+UseClassLoaderResourceCache
|
||
|
|
+ * -XX:DumpClassLoaderResourceCacheFile=clrct.log
|
||
|
|
+ * ClassLoaderResourceCacheTest
|
||
|
|
+ * @run testng/othervm
|
||
|
|
+ * --add-opens=java.base/java.net=ALL-UNNAMED
|
||
|
|
+ * --add-opens=java.base/jdk.internal.loader=ALL-UNNAMED
|
||
|
|
+ * -XX:+UnlockExperimentalVMOptions
|
||
|
|
+ * -XX:+UseClassLoaderResourceCache
|
||
|
|
+ * -XX:LoadClassLoaderResourceCacheFile=clrct.log
|
||
|
|
+ * ClassLoaderResourceCacheTest
|
||
|
|
+ */
|
||
|
|
+public class ClassLoaderResourceCacheTest {
|
||
|
|
+ private URL[] urls;
|
||
|
|
+ private URLClassLoader loader;
|
||
|
|
+ private Object loaderCache;
|
||
|
|
+
|
||
|
|
+ @BeforeMethod
|
||
|
|
+ public void initLoader() {
|
||
|
|
+ String classpath = System.getProperty("java.class.path");
|
||
|
|
+ String[] paths = classpath.split(File.pathSeparator);
|
||
|
|
+ urls = Arrays.stream(paths).map(s -> {
|
||
|
|
+ if (s.endsWith(".jar")) {
|
||
|
|
+ s = "jar:file:" + s + "!/";
|
||
|
|
+ } else {
|
||
|
|
+ s = "file:" + s + "/";
|
||
|
|
+ }
|
||
|
|
+ try {
|
||
|
|
+ return new URL(s);
|
||
|
|
+ } catch (MalformedURLException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ }
|
||
|
|
+ return null;
|
||
|
|
+ }).filter(Objects::nonNull).sorted(Comparator.comparing(URL::toExternalForm)).toArray(URL[]::new);
|
||
|
|
+ if (urls.length >= 2 && urls[0].toExternalForm().endsWith("ClassLoaderResourceCacheTest.d/")) {
|
||
|
|
+ URL tmp = urls[0];
|
||
|
|
+ urls[0] = urls[1];
|
||
|
|
+ urls[1] = tmp;
|
||
|
|
+ }
|
||
|
|
+ loader = new URLClassLoader(ClassLoaderResourceCacheTest.class.getSimpleName(), urls, null);
|
||
|
|
+ loaderCache = Access.getClassLoaderResourceCache(loader);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ @Test
|
||
|
|
+ public void testIsFeatureOn() {
|
||
|
|
+ Assert.assertTrue(Boolean.getBoolean("jdk.jbooster.clrcache.enable"));
|
||
|
|
+ String dumpFilePath = System.getProperty("jdk.jbooster.clrcache.dump");
|
||
|
|
+ String loadFilePath = System.getProperty("jdk.jbooster.clrcache.load");
|
||
|
|
+ if (dumpFilePath != null) {
|
||
|
|
+ Assert.assertFalse(new File(dumpFilePath).isFile());
|
||
|
|
+ }
|
||
|
|
+ if (loadFilePath != null) {
|
||
|
|
+ Assert.assertTrue(new File(loadFilePath).isFile());
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ @Test
|
||
|
|
+ public void testLoaderURLs() {
|
||
|
|
+ Assert.assertNotNull(loaderCache);
|
||
|
|
+ Arrays.stream(urls).forEach(url -> System.out.println("URLClassLoader url: " + url));
|
||
|
|
+ Assert.assertTrue(urls[0].toExternalForm().endsWith("URLClassLoader/"));
|
||
|
|
+ Assert.assertTrue(urls[1].toExternalForm().endsWith("ClassLoaderResourceCacheTest.d/"));
|
||
|
|
+ int i;
|
||
|
|
+ for (i = 2; i < urls.length; ++i) {
|
||
|
|
+ if (urls[i].toExternalForm().contains("testng")) {
|
||
|
|
+ break;
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+ Assert.assertTrue(i < urls.length, "\"testng-xxx.jar\" must be in the paths!");
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ @Test
|
||
|
|
+ public void testCacheResource() {
|
||
|
|
+ String cs1 = ClassLoaderResourceCacheTest.class.getName().replace('.', '/') + ".class";
|
||
|
|
+ String cs2 = "com/huawei/Nonexistent.class";
|
||
|
|
+ String cs3 = "org/testng/Assert.class";
|
||
|
|
+ URL res;
|
||
|
|
+ Object entry;
|
||
|
|
+
|
||
|
|
+ // Try to access three resources.
|
||
|
|
+
|
||
|
|
+ Assert.assertEquals(Access.getResourceUrlCacheMap(loaderCache).size(), 0);
|
||
|
|
+
|
||
|
|
+ res = loader.getResource(cs1);
|
||
|
|
+ Assert.assertNotNull(res);
|
||
|
|
+ Assert.assertTrue(res.toExternalForm().contains("ClassLoaderResourceCacheTest.d"));
|
||
|
|
+
|
||
|
|
+ res = loader.getResource(cs2);
|
||
|
|
+ Assert.assertNull(res);
|
||
|
|
+
|
||
|
|
+ res = loader.getResource(cs3);
|
||
|
|
+ Assert.assertNotNull(res);
|
||
|
|
+ Assert.assertTrue(res.toExternalForm().contains("lib/testng-"));
|
||
|
|
+
|
||
|
|
+ // Check cache entries.
|
||
|
|
+
|
||
|
|
+ Assert.assertEquals(Access.getResourceUrlCacheMap(loaderCache).size(), 3);
|
||
|
|
+
|
||
|
|
+ entry = Access.getResourceCacheEntry(loaderCache, cs1);
|
||
|
|
+ Assert.assertNotNull(entry);
|
||
|
|
+ Assert.assertTrue(Access.getResourceCacheEntryURL(entry).toExternalForm().contains("ClassLoaderResourceCacheTest.d"));
|
||
|
|
+ Assert.assertTrue(Access.getResourceCacheEntryIndex(entry) >= 1);
|
||
|
|
+
|
||
|
|
+ entry = Access.getResourceCacheEntry(loaderCache, cs2);
|
||
|
|
+ Assert.assertNotNull(entry);
|
||
|
|
+ Assert.assertNull(Access.getResourceCacheEntryURL(entry));
|
||
|
|
+ Assert.assertEquals(Access.getResourceCacheEntryIndex(entry), -1);
|
||
|
|
+
|
||
|
|
+ entry = Access.getResourceCacheEntry(loaderCache, cs3);
|
||
|
|
+ Assert.assertNotNull(entry);
|
||
|
|
+ Assert.assertTrue(Access.getResourceCacheEntryURL(entry).toExternalForm().contains("lib/testng-"));
|
||
|
|
+ Assert.assertTrue(Access.getResourceCacheEntryIndex(entry) >= 2);
|
||
|
|
+
|
||
|
|
+ // Try to access the three resources again.
|
||
|
|
+
|
||
|
|
+ res = loader.getResource(cs1);
|
||
|
|
+ Assert.assertNotNull(res);
|
||
|
|
+ Assert.assertTrue(res.toExternalForm().contains("ClassLoaderResourceCacheTest.d"));
|
||
|
|
+
|
||
|
|
+ res = loader.getResource(cs2);
|
||
|
|
+ Assert.assertNull(res);
|
||
|
|
+
|
||
|
|
+ res = loader.getResource(cs3);
|
||
|
|
+ Assert.assertNotNull(res);
|
||
|
|
+ Assert.assertTrue(res.toExternalForm().contains("lib/testng-"));
|
||
|
|
+
|
||
|
|
+ // Recheck cache entries.
|
||
|
|
+
|
||
|
|
+ Assert.assertEquals(Access.getResourceUrlCacheMap(loaderCache).size(), 3);
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ @Test
|
||
|
|
+ public void testLoadClassFastException() {
|
||
|
|
+ String cs1 = ClassLoaderResourceCacheTest.class.getName();
|
||
|
|
+ String cs2 = "com.huawei.Nonexistent";
|
||
|
|
+ String cs3 = "org.testng.Assert";
|
||
|
|
+ Object entry;
|
||
|
|
+
|
||
|
|
+ Class<?> c1 = null;
|
||
|
|
+ Class<?> c2 = null;
|
||
|
|
+ Class<?> c3 = null;
|
||
|
|
+
|
||
|
|
+ // Try to load three classes.
|
||
|
|
+
|
||
|
|
+ Assert.assertEquals(Access.getClassNotFoundExceptionCacheMap(loaderCache).size(), 0);
|
||
|
|
+
|
||
|
|
+ try {
|
||
|
|
+ c1 = loader.loadClass(cs1);
|
||
|
|
+ } catch (ClassNotFoundException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ Assert.fail();
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ try {
|
||
|
|
+ c2 = loader.loadClass(cs2);
|
||
|
|
+ Assert.fail();
|
||
|
|
+ } catch (ClassNotFoundException ignored) {
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ try {
|
||
|
|
+ c3 = loader.loadClass(cs3);
|
||
|
|
+ } catch (ClassNotFoundException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ Assert.fail();
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ Assert.assertNotNull(c1);
|
||
|
|
+ Assert.assertNull(c2);
|
||
|
|
+ Assert.assertNotNull(c3);
|
||
|
|
+
|
||
|
|
+ Assert.assertEquals(Access.getClassNotFoundExceptionCacheMap(loaderCache).size(), 1);
|
||
|
|
+
|
||
|
|
+ // Retry to load three classes.
|
||
|
|
+
|
||
|
|
+ try {
|
||
|
|
+ c1 = loader.loadClass(cs1);
|
||
|
|
+ } catch (ClassNotFoundException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ Assert.fail();
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ try {
|
||
|
|
+ c2 = loader.loadClass(cs2);
|
||
|
|
+ Assert.fail();
|
||
|
|
+ } catch (ClassNotFoundException ignored) {
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ try {
|
||
|
|
+ c3 = loader.loadClass(cs3);
|
||
|
|
+ } catch (ClassNotFoundException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ Assert.fail();
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ Assert.assertEquals(Access.getClassNotFoundExceptionCacheMap(loaderCache).size(), 1);
|
||
|
|
+ Assert.assertEquals(Access.getResourceUrlCacheMap(loaderCache).size(), 0);
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
+
|
||
|
|
+class Access {
|
||
|
|
+ private static final Field loaderCacheGetter;
|
||
|
|
+ private static final Field resourceUrlCacheMapGetter;
|
||
|
|
+ private static final Field classNotFoundExceptionCacheMapGetter;
|
||
|
|
+ private static final MethodHandle resourceCacheGetter;
|
||
|
|
+ private static final MethodHandle resourceCacheEntryURLGetter;
|
||
|
|
+ private static final MethodHandle resourceCacheEntryIndexGetter;
|
||
|
|
+
|
||
|
|
+ static {
|
||
|
|
+ try {
|
||
|
|
+ MethodHandles.Lookup lookup = MethodHandles.lookup();
|
||
|
|
+ Class<?> clrClass = Class.forName("java.net.ClassLoaderResourceCache");
|
||
|
|
+ Class<?> rceClass = Class.forName("java.net.ClassLoaderResourceCache$ResourceCacheEntry");
|
||
|
|
+
|
||
|
|
+ loaderCacheGetter = URLClassLoader.class.getDeclaredField("loaderCache");
|
||
|
|
+ loaderCacheGetter.setAccessible(true);
|
||
|
|
+
|
||
|
|
+ resourceUrlCacheMapGetter = clrClass.getDeclaredField("resourceUrlCache");
|
||
|
|
+ resourceUrlCacheMapGetter.setAccessible(true);
|
||
|
|
+
|
||
|
|
+ classNotFoundExceptionCacheMapGetter = clrClass.getDeclaredField("classNotFoundExceptionCache");
|
||
|
|
+ classNotFoundExceptionCacheMapGetter.setAccessible(true);
|
||
|
|
+
|
||
|
|
+ Method m;
|
||
|
|
+
|
||
|
|
+ m = clrClass.getDeclaredMethod("getResourceCache", String.class);
|
||
|
|
+ m.setAccessible(true);
|
||
|
|
+ resourceCacheGetter = lookup.unreflect(m);
|
||
|
|
+
|
||
|
|
+ m = rceClass.getMethod("getURL");
|
||
|
|
+ m.setAccessible(true);
|
||
|
|
+ resourceCacheEntryURLGetter = lookup.unreflect(m);
|
||
|
|
+
|
||
|
|
+ m = rceClass.getMethod("getIndex");
|
||
|
|
+ m.setAccessible(true);
|
||
|
|
+ resourceCacheEntryIndexGetter = lookup.unreflect(m);
|
||
|
|
+ } catch (Exception e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ throw new RuntimeException(e);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static Object getClassLoaderResourceCache(URLClassLoader loader) {
|
||
|
|
+ try {
|
||
|
|
+ return loaderCacheGetter.get(loader);
|
||
|
|
+ } catch (IllegalAccessException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ throw new RuntimeException(e);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static Map<String, Object> getResourceUrlCacheMap(Object loaderCache) {
|
||
|
|
+ try {
|
||
|
|
+ @SuppressWarnings("unchecked")
|
||
|
|
+ Map<String, Object> res = (Map<String, Object>) resourceUrlCacheMapGetter.get(loaderCache);
|
||
|
|
+ return res;
|
||
|
|
+ } catch (IllegalAccessException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ throw new RuntimeException(e);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static Map<String, ClassNotFoundException> getClassNotFoundExceptionCacheMap(Object loaderCache) {
|
||
|
|
+ try {
|
||
|
|
+ @SuppressWarnings("unchecked")
|
||
|
|
+ Map<String, ClassNotFoundException> res = (Map<String, ClassNotFoundException>) classNotFoundExceptionCacheMapGetter.get(loaderCache);
|
||
|
|
+ return res;
|
||
|
|
+ } catch (IllegalAccessException e) {
|
||
|
|
+ e.printStackTrace();
|
||
|
|
+ throw new RuntimeException(e);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static Object getResourceCacheEntry(Object loaderCache, String name) {
|
||
|
|
+ try {
|
||
|
|
+ return resourceCacheGetter.invoke(loaderCache, name);
|
||
|
|
+ } catch (Throwable throwable) {
|
||
|
|
+ throwable.printStackTrace();
|
||
|
|
+ throw new RuntimeException(throwable);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static URL getResourceCacheEntryURL(Object resourceCache) {
|
||
|
|
+ try {
|
||
|
|
+ return (URL) resourceCacheEntryURLGetter.invoke(resourceCache);
|
||
|
|
+ } catch (Throwable throwable) {
|
||
|
|
+ throwable.printStackTrace();
|
||
|
|
+ throw new RuntimeException(throwable);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+
|
||
|
|
+ public static int getResourceCacheEntryIndex(Object resourceCache) {
|
||
|
|
+ try {
|
||
|
|
+ return (int) resourceCacheEntryIndexGetter.invoke(resourceCache);
|
||
|
|
+ } catch (Throwable throwable) {
|
||
|
|
+ throwable.printStackTrace();
|
||
|
|
+ throw new RuntimeException(throwable);
|
||
|
|
+ }
|
||
|
|
+ }
|
||
|
|
+}
|
||
|
|
--
|
||
|
|
2.19.1
|
||
|
|
|