Is static init guaranteed NOT to run if class is not accessed?
Asked Answered
J

1

5

I know there are many topics and resources about this, but I'm wondering about a very specific question (and it might take a very long time to check all sources for a definite answer).

I know that JVM/Dalvik guarantees that by the time you access a static field of a class (except for final static primitive values), the static fields of the class are already initialized. Is the opposite true as well? If I never access a class at all (e.g. because the switch-case code in another static method never reaches a certain branch), is it guaranteed that the VM does not initialize statics of this class?

Assume I have a class such as this:

public class Boo {
      public static int[] anything = new int[] { 2,3,4 };
      private static int[] something = new int[] { 5,6,7 }; // this may be much bigger as well

      public static final int[] getAndClear() {
           int[] st = something;
           something = null;
           return st;
      }
}

My application is a very special one (not typical in some aspects), and it may hold hundreds of classes such as Boo (generated by a code generator), where something may be an array of varying element count (so it may contain very many elements as well sometimes).

Depending on application input, many of these pregenerated classes might never get accessed. I do not want that a lot of int[] objects get initialized unnecessarily, eating up much memory.

Jeffereyjefferies answered 26/10, 2012 at 18:8 Comment(0)
P
9

I know that JVM/Dalvik guarantees that by the time you access a static field of a class (except for final static primitive values), the static fields of the class are already initialized.

This is mostly true but is not the case for certain static fields because of constant inlining. In

class A {
  public static final String FOO = "foo";

  static { System.out.println("loaded A"); }
}

public class B {
  public static void main(String... argv) {
    System.out.println("Got " + A.FOO);
  }
}

the JVM will print "Got foo" but will not print "loaded A". In fact, B will run even if A.class is not on the class-path, though at least one of A.java or A.class must be available when compiling B.java.


Is the opposite true as well? If I never access a class at all (e.g. because the switch-case code in another static method never reaches a certain branch), is it guaranteed that the VM does not initialize statics of this class?

Yes. The JLS lays out the exact conditions under which class loading and initialization occurs, so JVM implementations have no freedom to eagerly load or initialize classes.

12.4.1 is the chapter of interest.

12.4.1. When Initialization Occurs

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

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

The "immediately before" verbiage forbids any eagerness, and mandates what happens when two classes both try to perform one of the actions above -- there is a lock associated with a loaded but uninitialized class, and both wait until the thread that first acquires that lock performs the initialization.

The "and the field is not a constant variable (§4.12.4)" verbiage is the exception to the rule that I illustrated by class B using A.FOO above.


public static final int[] getAndClear() { ... }

should probably be synchronized because otherwise two threads might both get the same array instead of one receiving null. The class loader lock that I mention above does not protect getAndClear.

Polyphemus answered 26/10, 2012 at 18:13 Comment(4)
Thanks, very useful (including the first code example). To make sure I didn't miss anything in my coding in case of my code snippet: when getAndClear() is accessed for the first time, it will return the array (because static init created it), and it will return null for any further calls (because the first call set it to null). Is this correct?Jeffereyjefferies
@ThomasCalc, Correct. The first time either Boo.getAndClear or Boo.anything is accessed, Boo will be loaded. Please see my latest edit about a potential race condition.Polyphemus
FWIW, this is also covered in the JVM spec, but slightly differently: docs.oracle.com/javase/specs/jvms/se5.0/html/… The item of interest is that certain reflective calls can also cause initialization. It also says "may be initialized only as a result of", which speaks to the original question.Whitening
@fadden, Thanks for the citation. I suppose the JLS could just cite the JVM spec but would then have to explain the relationship between statements and expressions and specific bytecodes which would complicate the JLS.Polyphemus

© 2022 - 2024 — McMap. All rights reserved.