Preview: JDK-8055854 Examine overhead in java.net.URLClassLoader (stack-less exceptions)

Peter Levart peter.levart at gmail.com
Wed Oct 15 22:30:42 UTC 2014


Hi,

Recently a patch was applied to JDK9 for issue "JDK-8057936: 
java.net.URLClassLoader.findClass uses exceptions in control flow", 
which optimizes frequent exceptional path out of doPrivileged block in 
URLClassLoader.findClass() and replaces it with null return (which is 
not wrapped with PrivilegedActionException) and hence eliminates one 
exception construction. This was the low-hanging fruit. Here I present a 
similar-sized fruit which hangs a little higher...

The most time-consuming part of exception construction is "remembering 
the stack trace" - the Throwable.fillInStackTrace() method. Do we always 
need stack-traces?

ClassLoader API consists of two protected methods that are meant to be 
used for communication among ClassLoaders:

protected Class<?> loadClass(String name, boolean resolve) throws 
ClassNotFoundException

protected Class<?> findClass(String name) throws ClassNotFoundException


loadClass() is meant to call findClass() and parent loader's loadClass() 
and both of them are meant to be arranged in subclass overrides which 
call super methods. ClassNotFoundException thrown in that arrangement is 
used only to signal unsuccessful loading of a particular class from one 
ClassLoader to the other or from super method to overriding method where 
stack traces are not needed. ClassLoader.loadClass(String name) public 
method is the public API for programmatic access and Class.forName() 
public API is wired through it. VM upcall also appears to be using the 
public loadClass(String) method.

I searched for callers of protected loadClass(String name, boolean 
resolve) method and apart from some tests, all JDK code seems to calls 
this method either from overriding protected method of a subclass (the 
super call) or from protected loadClass(String name, boolean resolve) 
method of another ClassLoader that happens to have access to it either 
because the caller is in the same package as the callee or nested in the 
same enclosing class. The notable exceptions are:

sun.applet.AppletClassLoader (a subclasss of URLClassLoader): overrides 
the method with public method (potentially accessible as public API), 
calls super and propagates unchanged CNFE, but it also overrides 
findClass(), calls super and never propagates exceptions from super 
(throws it's own CNF exceptions with full stacks).

java.net.FactoryURLClassLoader (a subclass of URLClassLoader): overrides 
the method with public method (probably a mistake), calls super and 
propagates CNFE, but the class is package-private, so there's no public 
access to this method.

sun.misc.Launcher.AppClassLoader (a subclass of URLClassLoader): 
overrides the method with public method(probably a mistake), calls super 
and propagates CNFE, but the class is package-private, so there's no 
public access to this method.

And of course:

java.lang.ClassLoader: delegates to the method from public 
loadClass(String name) method.

That appears to be it.

So here's a preview of a patch that uses stack-less 
ClassNotFoundException(s) for intra/inter-class-loader communication and 
replaces them with full-stack variants on the public API border:

http://cr.openjdk.java.net/~plevart/jdk9-dev/ClassLoader.CNFE/webrev.01/

I also prepared a benchmark which shows the improvement (results 
attached as comments):

http://cr.openjdk.java.net/~plevart/jdk9-dev/ClassLoader.CNFE/CLBench.java

The loadSameClassFailureand loadSameClassSuccess are throughput 
benchmarks which constantly call loadClass() on the AppClassLoader with 
the same unexistent and existent class names respectively. 
loadSameClassFailure shows improvement because CNF exception thrown from 
ExtClassLoader (parent of AppClassLoader) has no stack.

The loadNewClassFailure and loadNewClassSuccess are one-shot benchmarks 
which run each shot on fresh VM instance. A shot loads 100,000 different 
classes. Both benchmarks show improvement because of 
intra/inter-class-loader communication is using stack-less CNF exceptions.

I guess there exist other internal Oracle benchmarks that could be run 
with this patch to get some more evidence of possible improvement.

So what is the effect on stack-traces? The following simple test:

package test;
public class Test {
     static void doIt() throws Exception  {
         Class.forName("XXX");
     }
     public static void main(String[] args) throws Exception {
         doIt();
     }
}


Prints using original JDK code:

Exception in thread "main" java.lang.ClassNotFoundException: XXX
     at java.net.URLClassLoader$1.run(URLClassLoader.java:372)
     at java.net.URLClassLoader$1.run(URLClassLoader.java:361)
     at java.security.AccessController.doPrivileged(Native Method)
     at java.net.URLClassLoader.findClass(URLClassLoader.java:360)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:426)
     at sun.misc.Launcher$AppClassLoader.loadClass(Launcher.java:317)
     at java.lang.ClassLoader.loadClass(ClassLoader.java:359)
     at java.lang.Class.forName0(Native Method)
     at java.lang.Class.forName(Class.java:264)
     at test.Test.doIt(Test.java:11)
     at test.Test.main(Test.java:15)

And using patched JDK code:

Exception in thread "main" java.lang.ClassNotFoundException: XXX
     at java.lang.ClassLoader.loadClass(ClassLoader.java:366)
     at java.lang.Class.forName0(Native Method)
     at java.lang.Class.forName(Class.java:264)
     at test.Test.doIt(Test.java:11)
     at test.Test.main(Test.java:15)


Some internal intra/inter-class-loader frames are missing, but the 
application stack-trace is visible. Is this too much hiding?


Regards, Peter




More information about the core-libs-dev mailing list