代码拉取完成,页面将自动刷新
同步操作将从 src-openEuler/openjdk-17 强制同步,此操作会覆盖自 Fork 仓库以来所做的任何修改,且无法恢复!!!
确定后同步将在后台操作,完成时将刷新页面,请耐心等待。
---
.../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
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。