3

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.

Community
  • 1
  • 1
greenoldman
  • 16,895
  • 26
  • 119
  • 185
  • 2
    That's up to the implementation. I know Hotspot uses tricks like causing segfaults in order to lazily detect certain conditions. – chrylis -cautiouslyoptimistic- Dec 31 '15 at 16:38
  • The problem with knowing "how" is that it could change at any time, so you can't actually depend on it. The most important detail is that initialisation is thread safe, beyond that you risk making assuming about the implementation which may not always be correct. – Peter Lawrey Dec 31 '15 at 16:41
  • @PeterLawrey, I am interested how it is done, that's all. – greenoldman Dec 31 '15 at 16:42
  • 1
    Segfaults -- segmentation faults, or bus errors -- have been in use for a long time. They were basically developed to support virtual memory. Every time your OS loads a page of virtual memory, it generates a segfault first. If the VM is set up to point unused classes to unused memory, well that'll generate a segfault. – markspace Dec 31 '15 at 16:42
  • 1
    If you're really interested, the OpenJDK is open source. Go have a look. – markspace Dec 31 '15 at 16:43
  • @chrylis, (and @markspace), so if I read you right, JVM sets `SomeClass` static data to non-existing memory, and waits for internal exception. If it is thrown -- it means the data has to be initialized, if not, the data are OK to use. Is it correct? – greenoldman Dec 31 '15 at 16:47
  • I'm not saying that's how it works--I'm just saying that the JVM could *choose* to do it that way, and Hotspot does similar things elsewhere. – chrylis -cautiouslyoptimistic- Dec 31 '15 at 19:41
  • @markspace, don't confuse page faults (primarily a VM issue) with segfaults (primarily a memory-protection issue). Hotspot's use of segfaults for VM operations is a clever abuse of the feature. – chrylis -cautiouslyoptimistic- Dec 31 '15 at 19:44
  • @chrylis Not really. I've implemented page faults with segfaults. It's the standard implementation, afaik. There's no difference between a page fault and any other fault on any hardware I've worked with, only with the way the fault is handled by the OS. – markspace Dec 31 '15 at 19:51

2 Answers2

2

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.

Matt Timmermans
  • 53,709
  • 3
  • 46
  • 87
1

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).

Andreas
  • 154,647
  • 11
  • 152
  • 247
  • Thank you very much, however if I am reading this right, you wrote about callee side, not caller, because in Java code (above) there is no reference to static data (except for static initializer). Yet you just showed me how to check the caller side... :-) Thank you for that idea! – greenoldman Dec 31 '15 at 17:09
  • @greenoldman I wasn't writing about callee/caller side, but showing how the generated class file includes the initialization code for static fields, so the JVM can simply execute that code when the class is loaded. The JVM doesn't need complex logic for initializing, because that logic is supplied by the class file (with special handling of constants). This is the answer to "how" static fields are initialized, which is what your question was about. – Andreas Dec 31 '15 at 17:51
  • Maybe I didn't make myself clear, Java works (in short) on demand when it comes to static data initialization, so I wonder if every `SomeClass.myStaticField` reference (caller) is wrapped internally with `if (SomeClass.alreadyInitialized...`. – greenoldman Dec 31 '15 at 18:48
  • Update, as @Matt wrote JVM uses pretty high-level commands, I used your code to check references -- referencing static data is translated to `getstatic`. So one question leads to another :-). – greenoldman Dec 31 '15 at 19:15
  • 1
    @greenoldman Answer updated to address how the static references in the bytecode triggers initializer logic. – Andreas Dec 31 '15 at 21:14