Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Does the JVM throw if an unused class is absent?

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?

like image 316
meriton Avatar asked Jan 31 '17 19:01

meriton


1 Answers

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.

like image 103
Holger Avatar answered Sep 20 '22 18:09

Holger