Projects
home:dingli:branches:openEuler:24.09
openjdk-17
_service:tar_scm:Add-Class-Loader-Resource-Cach...
Sign Up
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
File _service:tar_scm:Add-Class-Loader-Resource-Cache-module.patch of Package openjdk-17
--- .../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
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.
浙ICP备2022010568号-2