From d4f9253f52dfed9d9c33b81b374baf55db0cacce Mon Sep 17 00:00:00 2001 From: iklam Date: Mon, 13 Oct 2014 16:10:01 -0700 Subject: [PATCH] 8061651: Interface to the Lookup Index Cache to improve URLClassPath search time Summary: Implemented the interface in sun.misc.URLClassPath and corresponding JVM_XXX APIs Reviewed-by: mchung, acorn, jiangli, dholmes --- make/mapfiles/libjava/mapfile-vers | 5 +- src/share/classes/sun/misc/Launcher.java | 26 ++- src/share/classes/sun/misc/URLClassPath.java | 180 +++++++++++++++++- src/share/classes/sun/misc/VM.java | 5 +- src/share/javavm/export/jvm.h | 25 +++ src/share/native/java/lang/ClassLoader.c | 4 +- src/share/native/sun/misc/URLClassPath.c | 109 +++++++++++ .../misc/URLClassPath/EnableLookupCache.java | 66 +++++++ 8 files changed, 407 insertions(+), 13 deletions(-) create mode 100644 src/share/native/sun/misc/URLClassPath.c create mode 100644 test/sun/misc/URLClassPath/EnableLookupCache.java diff --git a/make/mapfiles/libjava/mapfile-vers b/make/mapfiles/libjava/mapfile-vers index 0dc76bf02..152053c8a 100644 --- a/make/mapfiles/libjava/mapfile-vers +++ b/make/mapfiles/libjava/mapfile-vers @@ -1,5 +1,5 @@ # -# Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. +# Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. # DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. # # This code is free software; you can redistribute it and/or modify it @@ -269,6 +269,9 @@ SUNWprivate_1.1 { Java_sun_reflect_Reflection_getCallerClass__; Java_sun_reflect_Reflection_getCallerClass__I; Java_sun_reflect_Reflection_getClassAccessFlags; + Java_sun_misc_URLClassPath_knownToNotExist0; + Java_sun_misc_URLClassPath_getLookupCacheURLs; + Java_sun_misc_URLClassPath_getLookupCacheForClassLoader; Java_sun_misc_Version_getJdkVersionInfo; Java_sun_misc_Version_getJdkSpecialVersion; Java_sun_misc_Version_getJvmVersionInfo; diff --git a/src/share/classes/sun/misc/Launcher.java b/src/share/classes/sun/misc/Launcher.java index 71b3d3bbc..f72f60e85 100644 --- a/src/share/classes/sun/misc/Launcher.java +++ b/src/share/classes/sun/misc/Launcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1998, 2011, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1998, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -162,6 +162,8 @@ public class Launcher { */ public ExtClassLoader(File[] dirs) throws IOException { super(getExtURLs(dirs), null, factory); + SharedSecrets.getJavaNetAccess(). + getURLClassPath(this).initLookupCache(this); } private static File[] getExtDirs() { @@ -285,11 +287,15 @@ public class Launcher { }); } + final URLClassPath ucp; + /* * Creates a new AppClassLoader */ AppClassLoader(URL[] urls, ClassLoader parent) { super(urls, parent, factory); + ucp = SharedSecrets.getJavaNetAccess().getURLClassPath(this); + ucp.initLookupCache(this); } /** @@ -305,6 +311,23 @@ public class Launcher { sm.checkPackageAccess(name.substring(0, i)); } } + + if (ucp.knownToNotExist(name)) { + // The class of the given name is not found in the parent + // class loader as well as its local URLClassPath. + // Check if this class has already been defined dynamically; + // if so, return the loaded class; otherwise, skip the parent + // delegation and findClass. + Class c = findLoadedClass(name); + if (c != null) { + if (resolve) { + resolveClass(c); + } + return c; + } + throw new ClassNotFoundException(name); + } + return (super.loadClass(name, resolve)); } @@ -386,6 +409,7 @@ public class Launcher { urls = new URL[0]; } bcp = new URLClassPath(urls, factory); + bcp.initLookupCache(null); } } diff --git a/src/share/classes/sun/misc/URLClassPath.java b/src/share/classes/sun/misc/URLClassPath.java index 11dae8197..f92b1ded0 100644 --- a/src/share/classes/sun/misc/URLClassPath.java +++ b/src/share/classes/sun/misc/URLClassPath.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1997, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1997, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -38,6 +38,7 @@ import java.util.jar.Attributes.Name; import java.net.JarURLConnection; import java.net.MalformedURLException; import java.net.URL; +import java.net.URLClassLoader; import java.net.URLConnection; import java.net.HttpURLConnection; import java.net.URLStreamHandler; @@ -52,6 +53,7 @@ import java.security.PrivilegedExceptionAction; import java.security.cert.Certificate; import sun.misc.FileURLMapper; import sun.net.util.URLUtil; +import sun.security.action.GetPropertyAction; /** * This class is used to maintain a search path of URLs for loading classes @@ -63,15 +65,18 @@ public class URLClassPath { final static String USER_AGENT_JAVA_VERSION = "UA-Java-Version"; final static String JAVA_VERSION; private static final boolean DEBUG; + private static final boolean DEBUG_LOOKUP_CACHE; private static final boolean DISABLE_JAR_CHECKING; static { JAVA_VERSION = java.security.AccessController.doPrivileged( - new sun.security.action.GetPropertyAction("java.version")); + new GetPropertyAction("java.version")); DEBUG = (java.security.AccessController.doPrivileged( - new sun.security.action.GetPropertyAction("sun.misc.URLClassPath.debug")) != null); + new GetPropertyAction("sun.misc.URLClassPath.debug")) != null); + DEBUG_LOOKUP_CACHE = (java.security.AccessController.doPrivileged( + new GetPropertyAction("sun.misc.URLClassPath.debugLookupCache")) != null); String p = java.security.AccessController.doPrivileged( - new sun.security.action.GetPropertyAction("sun.misc.URLClassPath.disableJarChecking")); + new GetPropertyAction("sun.misc.URLClassPath.disableJarChecking")); DISABLE_JAR_CHECKING = p != null ? p.equals("true") || p.equals("") : false; } @@ -149,6 +154,12 @@ public class URLClassPath { urls.add(0, url); path.add(url); + + if (lookupCacheURLs != null) { + // The lookup cache is no longer valid, since getLookupCache() + // does not consider the newly added url. + disableAllLookupCaches(); + } } } @@ -172,7 +183,8 @@ public class URLClassPath { */ public URL findResource(String name, boolean check) { Loader loader; - for (int i = 0; (loader = getLoader(i)) != null; i++) { + int[] cache = getLookupCache(name); + for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) { URL url = loader.findResource(name, check); if (url != null) { return url; @@ -195,7 +207,8 @@ public class URLClassPath { } Loader loader; - for (int i = 0; (loader = getLoader(i)) != null; i++) { + int[] cache = getLookupCache(name); + for (int i = 0; (loader = getNextLoader(cache, i)) != null; i++) { Resource res = loader.getResource(name, check); if (res != null) { return res; @@ -215,6 +228,7 @@ public class URLClassPath { final boolean check) { return new Enumeration() { private int index = 0; + private int[] cache = getLookupCache(name); private URL url = null; private boolean next() { @@ -222,7 +236,7 @@ public class URLClassPath { return true; } else { Loader loader; - while ((loader = getLoader(index++)) != null) { + while ((loader = getNextLoader(cache, index++)) != null) { url = loader.findResource(name, check); if (url != null) { return true; @@ -262,6 +276,7 @@ public class URLClassPath { final boolean check) { return new Enumeration() { private int index = 0; + private int[] cache = getLookupCache(name); private Resource res = null; private boolean next() { @@ -269,7 +284,7 @@ public class URLClassPath { return true; } else { Loader loader; - while ((loader = getLoader(index++)) != null) { + while ((loader = getNextLoader(cache, index++)) != null) { res = loader.getResource(name, check); if (res != null) { return true; @@ -298,6 +313,151 @@ public class URLClassPath { return getResources(name, true); } + private static volatile boolean lookupCacheEnabled + = "true".equals(VM.getSavedProperty("sun.cds.enableSharedLookupCache")); + private URL[] lookupCacheURLs; + private ClassLoader lookupCacheLoader; + + synchronized void initLookupCache(ClassLoader loader) { + if ((lookupCacheURLs = getLookupCacheURLs(loader)) != null) { + lookupCacheLoader = loader; + } else { + // This JVM instance does not support lookup cache. + disableAllLookupCaches(); + } + } + + static void disableAllLookupCaches() { + lookupCacheEnabled = false; + } + + private static native URL[] getLookupCacheURLs(ClassLoader loader); + private static native int[] getLookupCacheForClassLoader(ClassLoader loader, + String name); + private static native boolean knownToNotExist0(ClassLoader loader, + String className); + + synchronized boolean knownToNotExist(String className) { + if (lookupCacheURLs != null && lookupCacheEnabled) { + return knownToNotExist0(lookupCacheLoader, className); + } + + // Don't know if this class exists or not -- need to do a full search. + return false; + } + + /** + * Returns an array of the index to lookupCacheURLs that may + * contain the specified resource. The values in the returned + * array are in strictly ascending order and must be a valid index + * to lookupCacheURLs array. + * + * This method returns an empty array if the specified resource + * cannot be found in this URLClassPath. If there is no lookup + * cache or it's disabled, this method returns null and the lookup + * should search the entire classpath. + * + * Example: if lookupCacheURLs contains {a.jar, b.jar, c.jar, d.jar} + * and package "foo" only exists in a.jar and c.jar, + * getLookupCache("foo/Bar.class") will return {0, 2} + * + * @param name the resource name + * @return an array of the index to lookupCacheURLs that may contain the + * specified resource; or null if no lookup cache is used. + */ + private synchronized int[] getLookupCache(String name) { + if (lookupCacheURLs == null || !lookupCacheEnabled) { + return null; + } + + int[] cache = getLookupCacheForClassLoader(lookupCacheLoader, name); + if (cache != null && cache.length > 0) { + int maxindex = cache[cache.length - 1]; // cache[] is strictly ascending. + if (!ensureLoaderOpened(maxindex)) { + if (DEBUG_LOOKUP_CACHE) { + System.out.println("Expanded loaders FAILED " + + loaders.size() + " for maxindex=" + maxindex); + } + return null; + } + } + + return cache; + } + + private boolean ensureLoaderOpened(int index) { + if (loaders.size() <= index) { + // Open all Loaders up to, and including, index + if (getLoader(index) == null) { + return false; + } + if (!lookupCacheEnabled) { + // cache was invalidated as the result of the above call. + return false; + } + if (DEBUG_LOOKUP_CACHE) { + System.out.println("Expanded loaders " + loaders.size() + + " to index=" + index); + } + } + return true; + } + + /* + * The CLASS-PATH attribute was expanded by the VM when building + * the resource lookup cache in the same order as the getLoader + * method does. This method validates if the URL from the lookup + * cache matches the URL of the Loader at the given index; + * otherwise, this method disables the lookup cache. + */ + private synchronized void validateLookupCache(int index, + String urlNoFragString) { + if (lookupCacheURLs != null && lookupCacheEnabled) { + if (index < lookupCacheURLs.length && + urlNoFragString.equals( + URLUtil.urlNoFragString(lookupCacheURLs[index]))) { + return; + } + if (DEBUG || DEBUG_LOOKUP_CACHE) { + System.out.println("WARNING: resource lookup cache invalidated " + + "for lookupCacheLoader at " + index); + } + disableAllLookupCaches(); + } + } + + /** + * Returns the next Loader that may contain the resource to + * lookup. If the given cache is null, return loaders.get(index) + * that may be lazily created; otherwise, cache[index] is the next + * Loader that may contain the resource to lookup and so returns + * loaders.get(cache[index]). + * + * If cache is non-null, loaders.get(cache[index]) must be present. + * + * @param cache lookup cache. If null, search the entire class path + * @param index index to the given cache array; or to the loaders list. + */ + private synchronized Loader getNextLoader(int[] cache, int index) { + if (closed) { + return null; + } + if (cache != null) { + if (index < cache.length) { + Loader loader = loaders.get(cache[index]); + if (DEBUG_LOOKUP_CACHE) { + System.out.println("HASCACHE: Loading from : " + cache[index] + + " = " + loader.getBaseURL()); + } + return loader; + } else { + return null; // finished iterating over cache[] + } + } else { + return getLoader(index); + } + } + /* * Returns the Loader at the specified position in the URL search * path. The URLs are opened and expanded as needed. Returns null @@ -341,9 +501,13 @@ public class URLClassPath { continue; } // Finally, add the Loader to the search path. + validateLookupCache(loaders.size(), urlNoFragString); loaders.add(loader); lmap.put(urlNoFragString, loader); } + if (DEBUG_LOOKUP_CACHE) { + System.out.println("NOCACHE: Loading from : " + index ); + } return loaders.get(index); } diff --git a/src/share/classes/sun/misc/VM.java b/src/share/classes/sun/misc/VM.java index f317b5daf..4ae2664b7 100644 --- a/src/share/classes/sun/misc/VM.java +++ b/src/share/classes/sun/misc/VM.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -313,6 +313,9 @@ public class VM { // used by sun.launcher.LauncherHelper props.remove("sun.java.launcher.diag"); + + // used by sun.misc.URLClassPath + props.remove("sun.cds.enableSharedLookupCache"); } // Initialize any miscellenous operating system settings that need to be diff --git a/src/share/javavm/export/jvm.h b/src/share/javavm/export/jvm.h index 1aaec31c3..611120f25 100644 --- a/src/share/javavm/export/jvm.h +++ b/src/share/javavm/export/jvm.h @@ -1391,6 +1391,31 @@ JVM_GetThreadStateValues(JNIEnv* env, jint javaThreadState); JNIEXPORT jobjectArray JNICALL JVM_GetThreadStateNames(JNIEnv* env, jint javaThreadState, jintArray values); +/* + * Returns true if the JVM's lookup cache indicates that this class is + * known to NOT exist for the given loader. + */ +JNIEXPORT jboolean JNICALL +JVM_KnownToNotExist(JNIEnv *env, jobject loader, const char *classname); + +/* + * Returns an array of all URLs that are stored in the JVM's lookup cache + * for the given loader. NULL if the lookup cache is unavailable. + */ +JNIEXPORT jobjectArray JNICALL +JVM_GetResourceLookupCacheURLs(JNIEnv *env, jobject loader); + +/* + * Returns an array of all URLs that *may* contain the resource_name for the + * given loader. This function returns an integer array, each element + * of which can be used to index into the array returned by + * JVM_GetResourceLookupCacheURLs of the same loader to determine the + * URLs. + */ +JNIEXPORT jintArray JNICALL +JVM_GetResourceLookupCache(JNIEnv *env, jobject loader, const char *resource_name); + + /* ========================================================================= * The following defines a private JVM interface that the JDK can query * for the JVM version and capabilities. sun.misc.Version defines diff --git a/src/share/native/java/lang/ClassLoader.c b/src/share/native/java/lang/ClassLoader.c index f6d058399..9a4c5db74 100644 --- a/src/share/native/java/lang/ClassLoader.c +++ b/src/share/native/java/lang/ClassLoader.c @@ -1,5 +1,5 @@ /* - * Copyright (c) 1996, 2013, Oracle and/or its affiliates. All rights reserved. + * Copyright (c) 1996, 2014, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it @@ -51,7 +51,7 @@ Java_java_lang_ClassLoader_registerNatives(JNIEnv *env, jclass cls) /* Convert java string to UTF char*. Use local buffer if possible, otherwise malloc new memory. Returns null IFF malloc failed. */ -static char* +char* getUTF(JNIEnv *env, jstring str, char* localBuf, int bufSize) { char* utfStr = NULL; diff --git a/src/share/native/sun/misc/URLClassPath.c b/src/share/native/sun/misc/URLClassPath.c new file mode 100644 index 000000000..16380c9d1 --- /dev/null +++ b/src/share/native/sun/misc/URLClassPath.c @@ -0,0 +1,109 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. Oracle designates this + * particular file as subject to the "Classpath" exception as provided + * by Oracle in the LICENSE file that accompanied this code. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +#include + +#include "jni.h" +#include "jni_util.h" +#include "jlong.h" +#include "jvm.h" +#include "jdk_util.h" + +#include "sun_misc_URLClassPath.h" + +extern char* +getUTF(JNIEnv *env, jstring str, char* localBuf, int bufSize); + + +JNIEXPORT jboolean JNICALL +Java_sun_misc_URLClassPath_knownToNotExist0(JNIEnv *env, jclass cls, jobject loader, + jstring classname) +{ + char *clname; + jboolean result = JNI_FALSE; + char buf[128]; + + if (classname == NULL) { + JNU_ThrowNullPointerException(env, NULL); + return result; + } + + clname = getUTF(env, classname, buf, sizeof(buf)); + if (clname == NULL) { + JNU_ThrowOutOfMemoryError(env, NULL); + return result; + } + VerifyFixClassname(clname); + + if (!VerifyClassname(clname, JNI_TRUE)) { /* expects slashed name */ + goto done; + } + + result = JVM_KnownToNotExist(env, loader, clname); + + done: + if (clname != buf) { + free(clname); + } + + return result; +} + +JNIEXPORT jobjectArray JNICALL +Java_sun_misc_URLClassPath_getLookupCacheURLs(JNIEnv *env, jclass cls, jobject loader) +{ + return JVM_GetResourceLookupCacheURLs(env, loader); +} + + +JNIEXPORT jintArray JNICALL +Java_sun_misc_URLClassPath_getLookupCacheForClassLoader(JNIEnv *env, jclass cls, + jobject loader, + jstring resource_name) +{ + char *resname; + jintArray result = NULL; + char buf[128]; + + if (resource_name == NULL) { + JNU_ThrowNullPointerException(env, NULL); + return result; + } + + resname = getUTF(env, resource_name, buf, sizeof(buf)); + if (resname == NULL) { + JNU_ThrowOutOfMemoryError(env, NULL); + return result; + } + result = JVM_GetResourceLookupCache(env, loader, resname); + + done: + if (resname != buf) { + free(resname); + } + + return result; +} + diff --git a/test/sun/misc/URLClassPath/EnableLookupCache.java b/test/sun/misc/URLClassPath/EnableLookupCache.java new file mode 100644 index 000000000..2ff3801ce --- /dev/null +++ b/test/sun/misc/URLClassPath/EnableLookupCache.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2014, Oracle and/or its affiliates. All rights reserved. + * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. + * + * This code is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 only, as + * published by the Free Software Foundation. + * + * This code is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * version 2 for more details (a copy is included in the LICENSE file that + * accompanied this code). + * + * You should have received a copy of the GNU General Public License version + * 2 along with this work; if not, write to the Free Software Foundation, + * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. + * + * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA + * or visit www.oracle.com if you need additional information or have any + * questions. + */ + +/* @test + * @bug 8061651 + * @summary -Dsun.cds.enableSharedLookupCache specified on the command-line + * should have no effect. + * @run main/othervm -Dsun.cds.enableSharedLookupCache=true -Xshare:off -Dfoo.foo.bar=xyz EnableLookupCache + */ + +public class EnableLookupCache { + public static void main(String[] args) throws Exception { + // If JVM is started with -Xshare:off, the sun.cds.enableSharedLookupCache + // should never be true, even if it has been explicitly set in the + // command-line. + String prop = "sun.cds.enableSharedLookupCache"; + String value = System.getProperty(prop); + System.out.println("System.getProperty(\"" + prop + "\") = \"" + value+ "\""); + + if ("true".equals(value)) { + System.out.println("Test FAILED: system property " + prop + + " is \"true\" (unexpected)"); + throw new RuntimeException(prop + " should not be " + value); + } + + // Make sure the -D... arguments in the @run tag are indeed used. + prop = "foo.foo.bar"; + value = System.getProperty(prop); + System.out.println("System.getProperty(\"" + prop + "\") = \"" + value+ "\""); + if (!"xyz".equals(value)) { + System.out.println("Test FAILED: system property " + prop + + " should be \"xyz\" -- is JTREG set up properly?"); + throw new RuntimeException(prop + " should not be " + value); + } + + + // We should be able to load the other classes without issue. + A.test(); + B.test(); + System.out.println("Test PASSED"); + } + + static class A {static void test() {}} + static class B {static void test() {}} +} + -- GitLab