12

By default, Sun's JVM both lazily loads classes and lazily initializes (i.e. calls their <clinit> methods) them. Consider the following class, ClinitBomb, which throws an Exception during a static{} block.

public class ClinitBomb {
    static {
        explode();
    }   
    private static void explode() {
        throw new RuntimeException("boom!");
    }       
}

Now, consider how to trigger the bomb:

public class Main {
    public static void main(String[] args) {
        System.out.println("A");
        try {
            Class.forName("ClinitBomb");
        } catch (Exception e) {
            e.printStackTrace(System.out);
        }
        System.out.println("B");
        ClinitBomb o2 = new ClinitBomb();
        System.out.println("C");
    }
}

We're guaranteed the explosion happens before point B, since forName's documentation says so; the question is whether it happens before point A (when Main is loaded.) In Sun's JVM, even though main() contains a static reference to ClinitBomb, it happens after A.

I want a way to tell the JVM to load and initialize ClinitBomb as soon as it initializes Main (so the bomb explodes before point A.) In general, I want a way to say, "whenever loading/initializing class X, also do so for any classes Y it references."

Is there a way to do that?

jade
  • 744
  • 5
  • 16
  • A static block referring to ClinitBomb in Main? – Thorbjørn Ravn Andersen Dec 13 '11 at 23:28
  • @Thorbjørn Ravn Andersen that won't solve the general problem. But I suppose a custom class loader could inject static blocks in each and every loaded class. – emory Dec 13 '11 at 23:36
  • Short of adding a huge list of `Class.forName()` I don't know. OTOH, why is it so important? – BillRobertson42 Dec 13 '11 at 23:37
  • @Bill I got left with a project which declared static fields initialized to a per-class logger, but getting the RMI-logger requires non-trivial work. When that's the part of the code that's broken, it'd be really nice to have the static initializers execute in a more obvious order. – jade Dec 16 '11 at 06:26
  • I suggest you move the initialization into a class then and call it from somewhere early in startup, and then reference it later. – BillRobertson42 Dec 16 '11 at 22:00

2 Answers2

10

There is no way to do this. The JLS says, in §12.4.1 When Initialization Occurs (emphasis mine):

Initialization of a class consists of executing its static initializers and the initializers for static fields declared in the class. [...]

A class or interface type T will be initialized immediately before the first occurrence of any one of the following:

  • T is a class and an instance of T is created.
  • T is a class and a static method declared by T is invoked.
  • A static field declared by T is assigned.
  • A static field declared by T is used and the field is not a constant variable (§4.12.4).
  • T is a top-level class, and an assert statement (§14.10) lexically nested within T is executed.

Invocation of certain reflective methods in class Class and in package java.lang.reflect also causes class or interface initialization. A class or interface will not be initialized under any other circumstance.

A Java implementation which initialized classes as soon as they were loaded would violate the JLS.

Although what you could do would be to use the JVM instrumentation API to write a ClassFileTransformer which added a static block to every class which explicitly initialized its referenced classes (via Class.forName, probably). As soon as one class gets initialized, all the classes reachable from it will be initialized. That might give you the result you're after. It's quite a lot of work, though!

Tom Anderson
  • 46,189
  • 17
  • 92
  • 133
  • 1
    Thanks. (Also, bummer. It would have been so convenient for debugging in this case. I inherited a codebase in which most classes declare a `static final Logger` field, which is great, except that they're created using a logging infrastructure which hits up RMI. So, basically, at reasonably arbitrary points in execution, you suddenly have remote calls where a failure gives you an `ExceptionInInitializerError`. Doesn't it sound fun?) – jade Dec 14 '11 at 03:32
  • Hmm.. interestingly enough, ClinitBomb's class isn't even *loaded* until the forName(), which seems really odd. (That is: removing ClinitBomb.class still lets main print "A" before throwing a NoClassDefFoundError.) According to JLS 12.1.2, it's implementation-dependent; any idea how to change the default behavior in the Sun JVM? – jade Dec 14 '11 at 03:42
  • I'm not aware of one. Run `java -XX:+UnlockDiagnosticVMOptions -XX:+PrintFlagsFinal` and see if anything suggests itself - i couldn't see anything with the JRE i have. – Tom Anderson Dec 14 '11 at 19:21
  • It looks like -XX:+TraceClassResolution, in addition to dumping a log of what's resolving to what, also forces it to happen eagerly. We've since found and fixed :) the bug, but this'll be handy the next time our ClassLoaders break. Thanks! – jade Dec 16 '11 at 06:23
1
Class.forName("...", true /*initialize*/, getClassLoader());

You were halfways there.

Joop Eggen
  • 107,315
  • 7
  • 83
  • 138
  • You could use your own eager ClassLoader. – Joop Eggen Dec 13 '11 at 23:36
  • No. The code he's written will already initialize `ClinitBomb` in the `forName` call; the [single-argument version of forName](http://docs.oracle.com/javase/6/docs/api/java/lang/Class.html#forName%28java.lang.String%29) "is equivalent to: Class.forName(className, true, currentLoader)". His desire is to have `ClinitBomb` be initialized even earlier than that. – Tom Anderson Dec 13 '11 at 23:46