greenoldman
greenoldman

Reputation: 21052

How static data are initialized?

There are a lot good answer to "when", like in this thread -- When does static class initialization happen? and now my question is "how". Here is the quote from the answer by Stephen C

A classes static initialization normally happens immediately before the first time one of the following events occurs:

  • an instance of the class is created,
  • a static method of the class is invoked,
  • a static field of the class is assigned,
  • a non-constant static field is used, or
  • for a top-level class, an assert statement lexically nested within the class is executed.

So how it is done internally? Each instruction that could trigger initialization is wrapped with if? Details for any working :-) implementation are fine with me.

I am tagging the question with "Java" but if I am not mistaken C# and Swift also initialize static data -- in general -- on demand.

Upvotes: 3

Views: 123

Answers (2)

Andreas
Andreas

Reputation: 159086

For static constants (final) fields, the .class file defines the constant value directly, so the JVM can assign it when the class is loaded.

For non-constant static fields, the compiler will merge any initializers with custom static initializer blocks to produce a single static initializer block of code, that the JVM can execute when the class is loaded.

Example:

public final class Test {
    public static double x = Math.random();
    static {
        x *= 2;
    }
    public static final double y = myInit();
    public static final double z = 3.14;
    private static double myInit() {
        return Math.random();
    }
}

Field z is a constant, while x and y are run-time values and will be merged with the static initializer block (the x *= 2).

If you disassemble the bytecode using javap -c -p -constants Test.class, you will get the following. I've added blank lines to separate the merged sections of the static initializer block (static {}).

Compiled from "Test.java"
public final class test.Test {
  public static double x;

  public static final double y;

  public static final double z = 3.14d;

  static {};
    Code:
       0: invokestatic  #15                 // Method java/lang/Math.random:()D
       3: putstatic     #21                 // Field x:D

       6: getstatic     #21                 // Field x:D
       9: ldc2_w        #23                 // double 2.0d
      12: dmul
      13: putstatic     #21                 // Field x:D

      16: invokestatic  #25                 // Method myInit:()D
      19: putstatic     #28                 // Field y:D

      22: return

  public test.Test();
    Code:
       0: aload_0
       1: invokespecial #33                 // Method java/lang/Object."<init>":()V
       4: return

  private static double myInit();
    Code:
       0: invokestatic  #15                 // Method java/lang/Math.random:()D
       3: dreturn
}

Note that this also shows that a default constructor was created by the compiler and that the constructor calls the superclass (Object) default constructor.


UPDATE

If you add the -v (verbose) argument to javap, you'll see the constant pool which stores the values defining those references listed above, e.g. for the Math.random() call, which is listed above as #15, the relevant constants are:

#15 = Methodref          #16.#18        // java/lang/Math.random:()D
#16 = Class              #17            // java/lang/Math
#17 = Utf8               java/lang/Math
#18 = NameAndType        #19:#20        // random:()D
#19 = Utf8               random
#20 = Utf8               ()D

As you can see, there is a Class constant (#16) for the Math class, which is defined as the string "java/lang/Math".

The first time reference #16 is used (which happens when executing invokestatic #15), the JVM will resolve it to an actual class. If that class has already been loaded, it'll just use that loaded class.

If the class has not yet been loaded, the ClassLoader is invoked to load the class (loadClass()), which in turn calls the defineClass() method, taking the bytecode as a parameter. During this loading process, the class is initialized, by automatically assigning constant values and executing the previously identified static initializer code block.

It is this class reference resolving process performed by the JVM that triggers the initialization of the static fields. This is essentially what happens, but the exact mechanics of this process is JVM implementation specific, e.g. by JIT (Just-In-Time compilation to machine code).

Upvotes: 1

Matt Timmermans
Matt Timmermans

Reputation: 59144

As mentioned in the comments, this sort of thing can be done with segfaults, but with Java this is not really necessary.

Remember that Java bytecode is not executed directly by the machine -- before it is JIT-compiled into real machine instructions, it is interpreted and profiled to determine when to compile it, and this already involves executing lots and lots of machine instructions for each bytecode instruction. It's no problem to check all the conditions of static initialization during this time.

Bytecode may also be compiled into machine code with checks, which is rewritten or patched after the checks are first executed. This sort of thing also happens for lots of other reasons like automatic inlining and escape analysis, so doing the static initialization checks like this is no big problem.

In short, there are lots of ways, but the key point is that when you run a Java program there is a whole lot going on besides the code you actually wrote.

Upvotes: 2

Related Questions