# HG changeset patch # User mchung # Date 1286853747 25200 # Node ID 637546039be33fbce7b60b17acf1be534b0599c0 # Parent 8310dc30a4addd6a06f5949e48fcb132db85519e 6977738: Deadlock between java.lang.ClassLoader and java.util.Properties Reviewed-by: alanb, sherman, darcy, igor diff -r 8310dc30a4ad -r 637546039be3 jdk/make/java/java/FILES_java.gmk --- a/jdk/make/java/java/FILES_java.gmk Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/make/java/java/FILES_java.gmk Mon Oct 11 20:22:27 2010 -0700 @@ -465,14 +465,11 @@ java/security/ProtectionDomain.java \ java/net/URLClassLoader.java \ java/net/URLConnection.java \ + sun/misc/BootClassLoaderHook.java \ sun/misc/Launcher.java \ sun/misc/MetaIndex.java \ sun/misc/URLClassPath.java \ sun/misc/Version.java \ - sun/net/www/protocol/jar/Handler.java \ - sun/net/www/protocol/jar/JarURLConnection.java \ - sun/net/www/protocol/file/Handler.java \ - sun/net/www/protocol/file/FileURLConnection.java \ sun/misc/FileURLMapper.java \ sun/misc/MessageUtils.java \ sun/misc/GC.java \ @@ -482,6 +479,10 @@ sun/misc/JavaIOFileDescriptorAccess.java \ sun/misc/JavaNioAccess.java \ sun/misc/Perf.java \ - sun/misc/PerfCounter.java + sun/misc/PerfCounter.java \ + sun/net/www/protocol/jar/Handler.java \ + sun/net/www/protocol/jar/JarURLConnection.java \ + sun/net/www/protocol/file/Handler.java \ + sun/net/www/protocol/file/FileURLConnection.java FILES_java = $(JAVA_JAVA_java) diff -r 8310dc30a4ad -r 637546039be3 jdk/src/share/classes/java/lang/Integer.java --- a/jdk/src/share/classes/java/lang/Integer.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/src/share/classes/java/lang/Integer.java Mon Oct 11 20:22:27 2010 -0700 @@ -586,25 +586,13 @@ * Cache to support the object identity semantics of autoboxing for values between * -128 and 127 (inclusive) as required by JLS. * - * The cache is initialized on first usage. During VM initialization the - * getAndRemoveCacheProperties method may be used to get and remove any system - * properites that configure the cache size. At this time, the size of the - * cache may be controlled by the -XX:AutoBoxCacheMax= option. + * The cache is initialized on first usage. The size of the cache + * may be controlled by the -XX:AutoBoxCacheMax= option. + * During VM initialization, java.lang.Integer.IntegerCache.high property + * may be set and saved in the private system properties in the + * sun.misc.VM class. */ - // value of java.lang.Integer.IntegerCache.high property (obtained during VM init) - private static String integerCacheHighPropValue; - - static void getAndRemoveCacheProperties() { - if (!sun.misc.VM.isBooted()) { - Properties props = System.getProperties(); - integerCacheHighPropValue = - (String)props.remove("java.lang.Integer.IntegerCache.high"); - if (integerCacheHighPropValue != null) - System.setProperties(props); // remove from system props - } - } - private static class IntegerCache { static final int low = -128; static final int high; @@ -613,6 +601,8 @@ static { // high value may be configured by property int h = 127; + String integerCacheHighPropValue = + sun.misc.VM.getSavedProperty("java.lang.Integer.IntegerCache.high"); if (integerCacheHighPropValue != null) { int i = parseInt(integerCacheHighPropValue); i = Math.max(i, 127); diff -r 8310dc30a4ad -r 637546039be3 jdk/src/share/classes/java/lang/System.java --- a/jdk/src/share/classes/java/lang/System.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/src/share/classes/java/lang/System.java Mon Oct 11 20:22:27 2010 -0700 @@ -53,7 +53,13 @@ */ public final class System { - /* First thing---register the natives */ + /* register the natives via the static initializer. + * + * VM will invoke the initializeSystemClass method to complete + * the initialization for this class separated from clinit. + * Note that to use properties set by the VM, see the constraints + * described in the initializeSystemClass method. + */ private static native void registerNatives(); static { registerNatives(); @@ -1096,17 +1102,21 @@ * Initialize the system class. Called after thread initialization. */ private static void initializeSystemClass() { - props = new Properties(); - initProperties(props); + // There are certain system configurations that may be controlled by + // VM options such as the maximum amount of direct memory and + // Integer cache size used to support the object identity semantics + // of autoboxing. Typically, the library will obtain these values + // from the properties set by the VM. If the properties are for + // internal implementation use only, these properties should be + // removed from the system properties. + // + // See java.lang.Integer.IntegerCache and the + // sun.misc.VM.saveAndRemoveProperties method for example. + props = initSystemProperties(); + lineSeparator = props.getProperty("line.separator"); sun.misc.Version.init(); - // Gets and removes system properties that configure the Integer - // cache used to support the object identity semantics of autoboxing. - // At this time, the size of the cache may be controlled by the - // vm option -XX:AutoBoxCacheMax=. - Integer.getAndRemoveCacheProperties(); - FileInputStream fdIn = new FileInputStream(FileDescriptor.in); FileOutputStream fdOut = new FileOutputStream(FileDescriptor.out); FileOutputStream fdErr = new FileOutputStream(FileDescriptor.err); @@ -1127,17 +1137,6 @@ // classes are used. sun.misc.VM.initializeOSEnvironment(); - // Set the maximum amount of direct memory. This value is controlled - // by the vm option -XX:MaxDirectMemorySize=. This method acts - // as an initializer only if it is called before sun.misc.VM.booted(). - sun.misc.VM.maxDirectMemory(); - - // Set a boolean to determine whether ClassLoader.loadClass accepts - // array syntax. This value is controlled by the system property - // "sun.lang.ClassLoader.allowArraySyntax". This method acts as - // an initializer only if it is called before sun.misc.VM.booted(). - sun.misc.VM.allowArraySyntax(); - // Subsystems that are invoked during initialization can invoke // sun.misc.VM.isBooted() in order to avoid doing things that should // wait until the application class loader has been set up. @@ -1152,6 +1151,18 @@ setJavaLangAccess(); } + private static Properties initSystemProperties() { + Properties props = new Properties(); + initProperties(props); // initialized by the VM + + // Save a private copy of the system properties object that + // can only be accessed by the internal implementation. Remove + // certain system properties that are not intended for public access. + sun.misc.VM.saveAndRemoveProperties(props); + + return props; + } + private static void setJavaLangAccess() { // Allow privileged classes outside of java.lang sun.misc.SharedSecrets.setJavaLangAccess(new sun.misc.JavaLangAccess(){ diff -r 8310dc30a4ad -r 637546039be3 jdk/src/share/classes/java/util/Properties.java --- a/jdk/src/share/classes/java/util/Properties.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/src/share/classes/java/util/Properties.java Mon Oct 11 20:22:27 2010 -0700 @@ -705,7 +705,7 @@ * Strings. */ @Deprecated - public synchronized void save(OutputStream out, String comments) { + public void save(OutputStream out, String comments) { try { store(out, comments); } catch (IOException e) { @@ -890,7 +890,7 @@ * @see #loadFromXML(InputStream) * @since 1.5 */ - public synchronized void storeToXML(OutputStream os, String comment) + public void storeToXML(OutputStream os, String comment) throws IOException { if (os == null) @@ -929,8 +929,7 @@ * @see #loadFromXML(InputStream) * @since 1.5 */ - public synchronized void storeToXML(OutputStream os, String comment, - String encoding) + public void storeToXML(OutputStream os, String comment, String encoding) throws IOException { if (os == null) diff -r 8310dc30a4ad -r 637546039be3 jdk/src/share/classes/java/util/XMLUtils.java --- a/jdk/src/share/classes/java/util/XMLUtils.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/src/share/classes/java/util/XMLUtils.java Mon Oct 11 20:22:27 2010 -0700 @@ -141,14 +141,13 @@ comments.appendChild(doc.createTextNode(comment)); } - Set keys = props.keySet(); - Iterator i = keys.iterator(); - while(i.hasNext()) { - String key = (String)i.next(); - Element entry = (Element)properties.appendChild( - doc.createElement("entry")); - entry.setAttribute("key", key); - entry.appendChild(doc.createTextNode(props.getProperty(key))); + synchronized (props) { + for (String key : props.stringPropertyNames()) { + Element entry = (Element)properties.appendChild( + doc.createElement("entry")); + entry.setAttribute("key", key); + entry.appendChild(doc.createTextNode(props.getProperty(key))); + } } emitDocument(doc, os, encoding); } diff -r 8310dc30a4ad -r 637546039be3 jdk/src/share/classes/java/util/zip/ZipFile.java --- a/jdk/src/share/classes/java/util/zip/ZipFile.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/src/share/classes/java/util/zip/ZipFile.java Mon Oct 11 20:22:27 2010 -0700 @@ -85,8 +85,7 @@ static { // A system prpperty to disable mmap use to avoid vm crash when // in-use zip file is accidently overwritten by others. - String prop = AccessController.doPrivileged( - new GetPropertyAction("sun.zip.disableMemoryMapping")); + String prop = sun.misc.VM.getSavedProperty("sun.zip.disableMemoryMapping"); usemmap = (prop == null || !(prop.length() == 0 || prop.equalsIgnoreCase("true"))); } diff -r 8310dc30a4ad -r 637546039be3 jdk/src/share/classes/sun/jkernel/DownloadManager.java --- a/jdk/src/share/classes/sun/jkernel/DownloadManager.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/src/share/classes/sun/jkernel/DownloadManager.java Mon Oct 11 20:22:27 2010 -0700 @@ -25,13 +25,18 @@ package sun.jkernel; import java.io.*; +import java.net.URLStreamHandlerFactory; +import java.net.URL; +import java.net.MalformedURLException; import java.security.*; import java.util.*; import java.util.concurrent.*; import java.util.jar.*; import java.util.zip.*; +import sun.misc.BootClassLoaderHook; import sun.misc.Launcher; -import sun.misc.BootClassLoaderHook; +import sun.misc.URLClassPath; +import sun.net.www.ParseUtil; /** * Handles the downloading of additional JRE components. The bootstrap class @@ -658,31 +663,61 @@ return getAppDataLocalLow() + getKernelJREDir(); } - /** - * Returns an array of JAR files which have been added to the boot strap - * class path since the JVM was first booted. - */ - public static synchronized File[] getAdditionalBootStrapPaths() { - return additionalBootStrapPaths != null ? additionalBootStrapPaths : - new File[0]; - } - - + // To be revisited: + // How DownloadManager maintains its bootstrap class path. + // sun.misc.Launcher.getBootstrapClassPath() returns + // DownloadManager.getBootstrapClassPath() instead. + // + // So should no longer need to lock the Launcher.class. + // In addition, additionalBootStrapPaths is not really needed + // if it obtains the initial bootclasspath during DownloadManager's + // initialization. private static void addEntryToBootClassPath(File path) { // Must acquire these locks in this order synchronized(Launcher.class) { - synchronized(DownloadManager.class) { + synchronized(DownloadManager.class) { File[] newBootStrapPaths = new File[ additionalBootStrapPaths.length + 1]; System.arraycopy(additionalBootStrapPaths, 0, newBootStrapPaths, 0, additionalBootStrapPaths.length); newBootStrapPaths[newBootStrapPaths.length - 1] = path; additionalBootStrapPaths = newBootStrapPaths; - Launcher.flushBootstrapClassPath(); + if (bootstrapClassPath != null) + bootstrapClassPath.addURL(getFileURL(path)); } } } + /** + * Returns the kernel's bootstrap class path which includes the additional + * JARs downloaded + */ + private static URLClassPath bootstrapClassPath = null; + private synchronized static + URLClassPath getBootClassPath(URLClassPath bcp, + URLStreamHandlerFactory factory) + { + if (bootstrapClassPath == null) { + bootstrapClassPath = new URLClassPath(bcp.getURLs(), factory); + for (File path : additionalBootStrapPaths) { + bootstrapClassPath.addURL(getFileURL(path)); + } + } + return bootstrapClassPath; + } + + private static URL getFileURL(File file) { + try { + file = file.getCanonicalFile(); + } catch (IOException e) {} + + try { + return ParseUtil.fileToEncodedURL(file); + } catch (MalformedURLException e) { + // Should never happen since we specify the protocol... + throw new InternalError(); + } + } /** * Scan through java.ext.dirs to see if the lib/ext directory is included. @@ -1680,8 +1715,10 @@ } } - public File[] getAdditionalBootstrapPaths() { - return DownloadManager.getAdditionalBootStrapPaths(); + public URLClassPath getBootstrapClassPath(URLClassPath bcp, + URLStreamHandlerFactory factory) + { + return DownloadManager.getBootClassPath(bcp, factory); } public boolean isCurrentThreadPrefetching() { diff -r 8310dc30a4ad -r 637546039be3 jdk/src/share/classes/sun/misc/BootClassLoaderHook.java --- a/jdk/src/share/classes/sun/misc/BootClassLoaderHook.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/src/share/classes/sun/misc/BootClassLoaderHook.java Mon Oct 11 20:22:27 2010 -0700 @@ -27,6 +27,8 @@ import java.io.File; import java.io.IOException; +import java.net.URLStreamHandlerFactory; +import sun.misc.URLClassPath; /** * BootClassLoaderHook defines an interface for a hook to inject @@ -94,20 +96,6 @@ } } - private static final File[] EMPTY_FILE_ARRAY = new File[0]; - - /** - * Returns bootstrap class paths added by the hook. - */ - public static File[] getBootstrapPaths() { - BootClassLoaderHook hook = getHook(); - if (hook != null) { - return hook.getAdditionalBootstrapPaths(); - } else { - return EMPTY_FILE_ARRAY; - } - } - /** * Returns a pathname of a JAR or class that the hook loads * per this loadClass request; or null. @@ -133,10 +121,13 @@ public abstract boolean loadLibrary(String libname); /** - * Returns additional boot class paths added by the hook that - * should be searched by the boot class loader. + * Returns a bootstrap class path constructed by the hook. + * + * @param bcp VM's bootstrap class path + * @param factory Launcher's URL stream handler */ - public abstract File[] getAdditionalBootstrapPaths(); + public abstract URLClassPath getBootstrapClassPath(URLClassPath bcp, + URLStreamHandlerFactory factory); /** * Returns true if the current thread is in the process of doing diff -r 8310dc30a4ad -r 637546039be3 jdk/src/share/classes/sun/misc/Launcher.java --- a/jdk/src/share/classes/sun/misc/Launcher.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/src/share/classes/sun/misc/Launcher.java Mon Oct 11 20:22:27 2010 -0700 @@ -47,7 +47,6 @@ import java.security.Permission; import java.security.ProtectionDomain; import java.security.CodeSource; -import sun.security.action.GetPropertyAction; import sun.security.util.SecurityConstants; import sun.net.www.ParseUtil; @@ -57,6 +56,8 @@ public class Launcher { private static URLStreamHandlerFactory factory = new Factory(); private static Launcher launcher = new Launcher(); + private static String bootClassPath = + System.getProperty("sun.boot.class.path"); public static Launcher getLauncher() { return launcher; @@ -227,7 +228,8 @@ File dir = new File(urls[i].getPath()).getParentFile(); if (dir != null && !dir.equals(prevDir)) { // Look in architecture-specific subdirectory first - String arch = System.getProperty("os.arch"); + // Read from the saved system properties to avoid deadlock + String arch = VM.getSavedProperty("os.arch"); if (arch != null) { File file = new File(new File(dir, arch), name); if (file.exists()) { @@ -377,19 +379,15 @@ } } - private static URLClassPath bootstrapClassPath; - - public static synchronized URLClassPath getBootstrapClassPath() { - if (bootstrapClassPath == null) { - String prop = AccessController.doPrivileged( - new GetPropertyAction("sun.boot.class.path")); + private static class BootClassPathHolder { + static final URLClassPath bcp; + static { URL[] urls; - if (prop != null) { - final String path = prop; + if (bootClassPath != null) { urls = AccessController.doPrivileged( new PrivilegedAction() { public URL[] run() { - File[] classPath = getClassPath(path); + File[] classPath = getClassPath(bootClassPath); int len = classPath.length; Set seenDirs = new HashSet(); for (int i = 0; i < len; i++) { @@ -410,25 +408,16 @@ } else { urls = new URL[0]; } - - bootstrapClassPath = new URLClassPath(urls, factory); - final File[] additionalBootStrapPaths = - BootClassLoaderHook.getBootstrapPaths(); - AccessController.doPrivileged(new PrivilegedAction() { - public Object run() { - for (int i=0; i -1) - directMemory = l; - } - } - return directMemory; } @@ -212,26 +190,82 @@ private static boolean defaultAllowArraySyntax = false; private static boolean allowArraySyntax = defaultAllowArraySyntax; - // If this method is invoked during VM initialization, it initializes the - // allowArraySyntax boolean based on the value of the system property + // The allowArraySyntax boolean is initialized during system initialization + // in the saveAndRemoveProperties method. + // + // It is initialized based on the value of the system property // "sun.lang.ClassLoader.allowArraySyntax". If the system property is not // provided, the default for 1.5 is "true". In 1.6, the default will be // "false". If the system property is provided, then the value of // allowArraySyntax will be equal to "true" if Boolean.parseBoolean() // returns "true". Otherwise, the field will be set to "false". // - // If this method is invoked after the VM is booted, it returns the - // allowArraySyntax boolean set during initialization. + public static boolean allowArraySyntax() { + return allowArraySyntax; + } + + /** + * Returns the system property of the specified key saved at + * system initialization time. This method should only be used + * for the system properties that are not changed during runtime. + * It accesses a private copy of the system properties so + * that user's locking of the system properties object will not + * cause the library to deadlock. + * + * Note that the saved system properties do not include + * the ones set by sun.misc.Version.init(). + * + */ + public static String getSavedProperty(String key) { + if (savedProps.isEmpty()) + throw new IllegalStateException("Should be non-empty if initialized"); + + return savedProps.getProperty(key); + } + + private static final Properties savedProps = new Properties(); + + // Save a private copy of the system properties and remove + // the system properties that are not intended for public access. // - public static boolean allowArraySyntax() { - if (!booted) { - String s - = System.getProperty("sun.lang.ClassLoader.allowArraySyntax"); - allowArraySyntax = (s == null - ? defaultAllowArraySyntax - : Boolean.parseBoolean(s)); + // This method can only be invoked during system initialization. + public static void saveAndRemoveProperties(Properties props) { + if (booted) + throw new IllegalStateException("System initialization has completed"); + + savedProps.putAll(props); + + // Set the maximum amount of direct memory. This value is controlled + // by the vm option -XX:MaxDirectMemorySize=. + // The maximum amount of allocatable direct buffer memory (in bytes) + // from the system property sun.nio.MaxDirectMemorySize set by the VM. + // The system property will be removed. + String s = (String)props.remove("sun.nio.MaxDirectMemorySize"); + if (s != null) { + if (s.equals("-1")) { + // -XX:MaxDirectMemorySize not given, take default + directMemory = Runtime.getRuntime().maxMemory(); + } else { + long l = Long.parseLong(s); + if (l > -1) + directMemory = l; + } } - return allowArraySyntax; + + // Set a boolean to determine whether ClassLoader.loadClass accepts + // array syntax. This value is controlled by the system property + // "sun.lang.ClassLoader.allowArraySyntax". + s = props.getProperty("sun.lang.ClassLoader.allowArraySyntax"); + allowArraySyntax = (s == null + ? defaultAllowArraySyntax + : Boolean.parseBoolean(s)); + + // Remove other private system properties + // used by java.lang.Integer.IntegerCache + props.remove("java.lang.Integer.IntegerCache.high"); + + // used by java.util.zip.ZipFile + props.remove("sun.zip.disableMemoryMapping"); } // Initialize any miscellenous operating system settings that need to be diff -r 8310dc30a4ad -r 637546039be3 jdk/test/java/lang/ClassLoader/deadlock/GetResource.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/jdk/test/java/lang/ClassLoader/deadlock/GetResource.java Mon Oct 11 20:22:27 2010 -0700 @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2010, 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. + */ + +import java.util.Properties; +import java.util.concurrent.CyclicBarrier; +import java.util.concurrent.BrokenBarrierException; +import java.io.IOException; +import java.net.URL; + +/* @test + * @bug 6977738 + * @summary Test ClassLoader.getResource() that should not deadlock + # if another thread is holding the system properties object + * + * @build GetResource + * @run main GetResource + */ + +public class GetResource { + CyclicBarrier go = new CyclicBarrier(2); + CyclicBarrier done = new CyclicBarrier(2); + Thread t1, t2; + public GetResource() { + t1 = new Thread() { + public void run() { + Properties prop = System.getProperties(); + synchronized (prop) { + System.out.println("Thread 1 ready"); + try { + go.await(); + prop.put("property", "value"); + prop.store(System.out, ""); + done.await(); // keep holding the lock until t2 finishes + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (BrokenBarrierException e) { + throw new RuntimeException(e); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + System.out.println("Thread 1 exits"); + } + }; + + t2 = new Thread() { + public void run() { + System.out.println("Thread 2 ready"); + try { + go.await(); // wait until t1 holds the lock of the system properties + + URL u1 = Thread.currentThread().getContextClassLoader().getResource("unknownresource"); + URL u2 = Thread.currentThread().getContextClassLoader().getResource("sun/util/resources/CalendarData.class"); + if (u2 == null) { + throw new RuntimeException("Test failed: resource not found"); + } + done.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (BrokenBarrierException e) { + throw new RuntimeException(e); + } + System.out.println("Thread 2 exits"); + } + }; + } + + public void run() throws Exception { + t1.start(); + t2.start(); + try { + t1.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + throw e; + } + try { + t2.join(); + } catch (InterruptedException e) { + e.printStackTrace(); + throw e; + } + } + + public static void main(String[] args) throws Exception { + new GetResource().run(); + } +} diff -r 8310dc30a4ad -r 637546039be3 jdk/test/sun/misc/BootClassLoaderHook/TestHook.java --- a/jdk/test/sun/misc/BootClassLoaderHook/TestHook.java Mon Oct 11 10:55:04 2010 +0100 +++ b/jdk/test/sun/misc/BootClassLoaderHook/TestHook.java Mon Oct 11 20:22:27 2010 -0700 @@ -24,7 +24,10 @@ import java.io.File; import java.util.TreeSet; import java.util.Set; +import java.net.URLStreamHandlerFactory; import sun.misc.BootClassLoaderHook; +import sun.misc.URLClassPath; + /* @test * @bug 6888802 @@ -68,10 +71,6 @@ for (String s : copy) { System.out.println(" Loaded " + s); } - - if (BootClassLoaderHook.getBootstrapPaths().length > 0) { - throw new RuntimeException("Unexpected returned value from getBootstrapPaths()"); - } } private static void testHook() throws Exception { @@ -98,8 +97,9 @@ return false; } - public File[] getAdditionalBootstrapPaths() { - return new File[0]; + public URLClassPath getBootstrapClassPath(URLClassPath bcp, + URLStreamHandlerFactory factory) { + return bcp; } public boolean isCurrentThreadPrefetching() {