Consider the program:
public class Test {
public static void main(String[] args) {
if (Arrays.asList(args).contains("--withFoo")) {
use(new Foo());
}
}
static void use(Foo foo) {
// do something with foo
}
}
Is Foo required in the runtime classpath if the program is launched without arguments?
Research
The Java Language Specification is rather vague when Linkage Errors are reported:
This specification allows an implementation flexibility as to when linking activities (and, because of recursion, loading) take place, provided that the semantics of the Java programming language are respected, that a class or interface is completely verified and prepared before it is initialized, and that errors detected during linkage are thrown at a point in the program where some action is taken by the program that might require linkage to the class or interface involved in the error.
My Tests indicate that LinkageErrors are only thrown when I actually use Foo
:
$ rm Foo.class
$ java Test
$ java Test --withFoo
Exception in thread "main" java.lang.NoClassDefFoundError: Foo
at Test.main(Test.java:11)
Caused by: java.lang.ClassNotFoundException: Foo
at java.net.URLClassLoader$1.run(Unknown Source)
at java.net.URLClassLoader$1.run(Unknown Source)
at java.security.AccessController.doPrivileged(Native Method)
at java.net.URLClassLoader.findClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
at sun.misc.Launcher$AppClassLoader.loadClass(Unknown Source)
at java.lang.ClassLoader.loadClass(Unknown Source)
... 1 more
Can this behaviour be relied upon? Or is there any mainstream JVM that links unused code? If so, how can I isolate unused code such that it is only linked if needed?
You need only small changes to your test code to answer that question.
Change the type hierarchy to
class Bar {}
class Foo extends Bar {}
and the program to
public class Test {
public static void main(String[] args) {
if (Arrays.asList(args).contains("--withFoo")) {
use(new Foo());
}
}
static void use(Bar foo) {
// don't need actual code
}
}
Now, the program will fail with an error, if Foo
is absent, even before entering the main
method (with HotSpot). The reason is that the verifier needs the definition of Foo
to check whether passing it to a method expecting Bar
is valid.
HotSpot takes a short-cut, not loading the type, if the types are an exact match or if the target type is java.lang.Object
, where the assignment is always valid. That's why your original code does not throw early when Foo
is absent.
The bottom line is that the exact point of time when an error is thrown is implementation dependent, e.g. might depend on the actual verifier implementation. All that is guaranteed is, as you already cited, that an attempt to perform an action that requires linkage will throw previously detected linkage errors. But it is perfectly possible that your program never gets so far to make an attempt.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With