Is catching NoClassDefFoundError more efficient than using reflection?

We are having some code that may run in a web container, an EJB container or stand-alone. When running in a web container, we need to use the javax.servlet.ServletContext to load resources from the system. In other containers, you can not rely on the ServletContext class being available on the classpath. That raised the question whether it is more efficient to use ServletContext directly and catch a NoClassDefFoundError if ServletContext is not on the classpath or whether it is better to use reflection. The answer is not exactly what I had expected. I started with writing some code to benchmark both scenarios:

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import javax.servlet.ServletContext;

public class Benchmark {

private final static MockContainer container = new MockContainer();

public static void main(String[] args) {
Benchmark bench = new Benchmark();
bench.run();
}

private void run() {
final long warmupCycles = 100L;
final long invocations = 100000L;
long i;
// warm up
for (i = 0; i < warmupCycles; i++) {
reflect();
}
// actual measurement
long startReflect = System.currentTimeMillis();
for (i = 0; i < invocations; i++) {
reflect();
}
long stopReflect = System.currentTimeMillis();

// warm up
for (i = 0; i < warmupCycles; i++) {
except();
}
// actual measurement
long startExcept = System.currentTimeMillis();
for (i = 0; i < invocations; i++) {
except();
}
long stopExcept = System.currentTimeMillis();

System.out.println("Total time in ms for " + invocations + " invocations using reflection: " +
(stopReflect - startReflect));
System.out.println("Total time in ms for " + invocations + " invocations catching exception: " +
(stopExcept - startExcept));
}

private boolean reflect() {
boolean result = false;
try {
Class<?> contextClass = Class.forName("javax.servlet.ServletContext");
Object context = container.getSPI(contextClass);
if (context != null) {
Method getResourceAsStream = contextClass.getMethod("getResourceAsStream", String.class);
getResourceAsStream.invoke(context, "");
result = true;
}
} catch (ClassNotFoundException e) {
} catch (NoSuchMethodException e) {
} catch (IllegalAccessException e) {
} catch (InvocationTargetException e) {
}
return result;
}

private boolean except() {
boolean result = false;
try {
ServletContext context = container.getSPI(ServletContext.class);
if (context != null) {
context.getResourceAsStream("");
result = true;
}
} catch (NoClassDefFoundError e) {
}
return result;
}


static class MockContainer {
public <T> T getSPI(Class<T> spi) {
return null;
}
}
}

The results showed a very clear trend on my Apple Powerbook G4 with Java version 1.5.0_06:

Total time in ms for 100000 invocations using reflection:   674
Total time in ms for 100000 invocations catching exception: 5

The above tests had been run with the ServletContext available on the classpath. Now I wanted to test the same code without ServletContext on the classpath. This is the result:

Exception in thread "main" java.lang.NoClassDefFoundError: javax/servlet/ServletContext

That result stumped me because the benchmark code is supposedly catching that error. So I did some research and found this in the Java Language Specification:

After Test is loaded, it must be initialized before main can be invoked. And Test, like all (class or interface) types, must be linked before it is initialized. Linking involves verification, preparation and (optionally) resolution...

Resolution is the process of checking symbolic references from Test to other classes and interfaces, by loading the other classes and interfaces that are mentioned and checking that the references are correct.

The resolution step is optional at the time of initial linkage. An implementation may resolve symbolic references from a class or interface that is being linked very early, even to the point of resolving all symbolic references from the classes and interfaces that are further referenced, recursively...

An implementation may instead choose to resolve a symbolic reference only when it is actively used; consistent use of this strategy for all symbolic references would represent the "laziest" form of resolution.

This leads me to conclude that the Apple Java VM resolves references early, certainly before my try/catch block gets a chance to be invoked. Furthermore, catching java.lang.NoClassDefFoundError is not portable across JVMs and we must use reflection.

Technorati:

Comments:

Post a Comment:
Comments are closed for this entry.
About

ritzmann

Search

Categories
Archives
« April 2014
MonTueWedThuFriSatSun
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
    
       
Today