---%<---
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class URLClassPathBug {
public static void main(String[] args) throws Exception {
// System.setSecurityManager(new SecurityManager() {
// public @Override void checkPermission(Permission perm) {}
// });
File j = new File(System.getProperty("java.home"), "lib/rt.jar");
assert j.isFile() : j;
URL base = j.toURI().toURL();
URL incorrect = base;
URL correct = new URL("jar:" + base + "!/");
for (URL u : new URL[] {incorrect, correct}) {
System.err.println("Checking: " + u);
List<Long> times = new ArrayList<Long>();
for (int i = 0; i < 20; i++) {
times.add(time(u));
}
Collections.sort(times);
System.err.printf(" median time: %5dusec\n", times.get(times.size() / 2));
debug(u);
}
}
private static long time(URL u) throws Exception {
ClassLoader l = new URLClassLoader(new URL[] {u});
System.gc();
long start = System.nanoTime();
for (int i = 0; i < 10; i++) {
l.getResource("nonexistent");
}
long end = System.nanoTime();
long usec = (end - start) / 1000;
System.err.printf(" %5dusec\n", usec);
return usec;
}
private static void debug(URL u) throws Exception {
ClassLoader l = new URLClassLoader(new URL[] {u});
l.getResource("nonexistent");
Field f = URLClassLoader.class.getDeclaredField("ucp");
f.setAccessible(true);
Object ucp = f.get(l);
f = ucp.getClass().getDeclaredField("loaders");
f.setAccessible(true);
System.err.println(" l.ucp.loaders=" + f.get(ucp));
}
}
---%<---
JDK 6 produces in a typical run:
---%<---
Checking: file:/space/jdk1.6.0_11/jre/lib/rt.jar
7823¿sec
3049¿sec
2404¿sec
17242¿sec
1820¿sec
2815¿sec
6870¿sec
1677¿sec
12400¿sec
1549¿sec
1604¿sec
1667¿sec
1559¿sec
1536¿sec
1522¿sec
1534¿sec
1548¿sec
1587¿sec
1585¿sec
1589¿sec
median time: 1667¿sec
l.ucp.loaders=[sun.misc.URLClassPath$JarLoader@1833955]
Checking: jar:file:/space/jdk1.6.0_11/jre/lib/rt.jar!/
5187¿sec
3655¿sec
3775¿sec
3512¿sec
3365¿sec
3178¿sec
3073¿sec
3251¿sec
3402¿sec
2852¿sec
2682¿sec
2721¿sec
2786¿sec
2753¿sec
2691¿sec
2660¿sec
2680¿sec
2676¿sec
2870¿sec
2614¿sec
median time: 2870¿sec
l.ucp.loaders=[sun.misc.URLClassPath$Loader@b66cc]
---%<---
If you treat 'jar'-protocol URLs consistently with all other URLs - which is to say, consider 'u' to be a classpath element if 'new URL(u, r)' is the correct way to refer to a classpath resource 'r' - then you should use 'jar:file:/x.jar!/' as a classpath entry representing the root directory entry of the file '/x.jar'. Indeed URLClassLoader accepts this usage in its constructor. (It always _returns_ the 'jar'-protocol URLs from calls to getResource.)
Unfortunately, there is a bug in URLClassPath which causes the correct URLs to be searched more slowly than others. URLClassPath.getLoader(URL) does not even check for the 'jar' protocol, falling back to the generic Loader implementation rather than the specialized JarLoader implementation. Loader.findResource uses an inefficient search mechanism, based on actually opening a URLConnection and treating an IOException as "missing".
URLCP.Loader also calls check(URL) whether the resource can be found or not, which in the presence of a SecurityManager forces creation of a FilePermission, which is relatively slow due to the need to canonicalize the file path.
Apparently Bugster is not Unicode-compliant; the special char in the source was intended to be GREEK SMALL LETTER MU.
import java.io.File;
import java.lang.reflect.Field;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
public class URLClassPathBug {
public static void main(String[] args) throws Exception {
// System.setSecurityManager(new SecurityManager() {
// public @Override void checkPermission(Permission perm) {}
// });
File j = new File(System.getProperty("java.home"), "lib/rt.jar");
assert j.isFile() : j;
URL base = j.toURI().toURL();
URL incorrect = base;
URL correct = new URL("jar:" + base + "!/");
for (URL u : new URL[] {incorrect, correct}) {
System.err.println("Checking: " + u);
List<Long> times = new ArrayList<Long>();
for (int i = 0; i < 20; i++) {
times.add(time(u));
}
Collections.sort(times);
System.err.printf(" median time: %5dusec\n", times.get(times.size() / 2));
debug(u);
}
}
private static long time(URL u) throws Exception {
ClassLoader l = new URLClassLoader(new URL[] {u});
System.gc();
long start = System.nanoTime();
for (int i = 0; i < 10; i++) {
l.getResource("nonexistent");
}
long end = System.nanoTime();
long usec = (end - start) / 1000;
System.err.printf(" %5dusec\n", usec);
return usec;
}
private static void debug(URL u) throws Exception {
ClassLoader l = new URLClassLoader(new URL[] {u});
l.getResource("nonexistent");
Field f = URLClassLoader.class.getDeclaredField("ucp");
f.setAccessible(true);
Object ucp = f.get(l);
f = ucp.getClass().getDeclaredField("loaders");
f.setAccessible(true);
System.err.println(" l.ucp.loaders=" + f.get(ucp));
}
}
---%<---
JDK 6 produces in a typical run:
---%<---
Checking: file:/space/jdk1.6.0_11/jre/lib/rt.jar
7823¿sec
3049¿sec
2404¿sec
17242¿sec
1820¿sec
2815¿sec
6870¿sec
1677¿sec
12400¿sec
1549¿sec
1604¿sec
1667¿sec
1559¿sec
1536¿sec
1522¿sec
1534¿sec
1548¿sec
1587¿sec
1585¿sec
1589¿sec
median time: 1667¿sec
l.ucp.loaders=[sun.misc.URLClassPath$JarLoader@1833955]
Checking: jar:file:/space/jdk1.6.0_11/jre/lib/rt.jar!/
5187¿sec
3655¿sec
3775¿sec
3512¿sec
3365¿sec
3178¿sec
3073¿sec
3251¿sec
3402¿sec
2852¿sec
2682¿sec
2721¿sec
2786¿sec
2753¿sec
2691¿sec
2660¿sec
2680¿sec
2676¿sec
2870¿sec
2614¿sec
median time: 2870¿sec
l.ucp.loaders=[sun.misc.URLClassPath$Loader@b66cc]
---%<---
If you treat 'jar'-protocol URLs consistently with all other URLs - which is to say, consider 'u' to be a classpath element if 'new URL(u, r)' is the correct way to refer to a classpath resource 'r' - then you should use 'jar:file:/x.jar!/' as a classpath entry representing the root directory entry of the file '/x.jar'. Indeed URLClassLoader accepts this usage in its constructor. (It always _returns_ the 'jar'-protocol URLs from calls to getResource.)
Unfortunately, there is a bug in URLClassPath which causes the correct URLs to be searched more slowly than others. URLClassPath.getLoader(URL) does not even check for the 'jar' protocol, falling back to the generic Loader implementation rather than the specialized JarLoader implementation. Loader.findResource uses an inefficient search mechanism, based on actually opening a URLConnection and treating an IOException as "missing".
URLCP.Loader also calls check(URL) whether the resource can be found or not, which in the presence of a SecurityManager forces creation of a FilePermission, which is relatively slow due to the need to canonicalize the file path.
Apparently Bugster is not Unicode-compliant; the special char in the source was intended to be GREEK SMALL LETTER MU.
- relates to
-
JDK-8174151 URLClassLoader no longer uses custom URLStreamHandler for jar URLs
-
- Closed
-