⚙️ JVM Internals & Memory · Advanced

When a class is initialized in Java

Lazy initialization on first active use; static init order.

🧩 The mysteryA class's static block prints "init". You read one of its static fields... and nothing prints. You read another... and suddenly it does. What decides?

Loaded is not initialized

A class is initialized lazily, on its first active use: creating an instance with new, calling a static method, reading or writing a non-constant static field, or Class.forName(name). Then its static initializers and static assignments run once, top to bottom in source order.

🔮 Predict it

Constant or not?

What does this print?

class Cfg {
    static { System.out.println("init"); }
    static final String NAME = "app";
    static String mode() { return "dev"; }
}
void main() {
    System.out.println(Cfg.NAME);
    System.out.println(Cfg.mode());
}
  1. init app dev
  2. app init dev
  3. app dev
  4. init app init dev
Show the answer

NAME is a compile-time constant (static final with a constant value), so javac copies "app" straight into main; Cfg isn't touched. Calling mode() is an active use, so init prints just before dev.

What does NOT trigger it

These leave the class uninitialized: a class literal like Foo.class, creating an array like new Foo[10] (that only makes the array type) and reading a compile-time constant. By contrast, Class.forName("app.Config") loads and initializes by default.

var c = Foo.class;        // no init
var arr = new Foo[10];    // no init
Class.forName("app.Foo"); // init!
🔮 Predict it

Parents go first

What does this print?

class Base {
    static { System.out.println("Base"); }
}
class Kid extends Base {
    static { System.out.println("Kid"); }
}
void main() {
    new Kid();
}
  1. Kid Base
  2. Base Kid
  3. Kid
Show the answer

A superclass is initialized before its subclass, so Base prints first. Careful: if you only read a static field *declared* in Base through Kid, like Kid.n, only Base is initialized.

⚠️ The trap

Reading a field too early

Initializers run top to bottom. When max = calc() runs, base has only its default value 0 from the preparation step, so max ends up 0, not 200. Only afterwards is base set to 100.

class Limits {
    static int max = calc();  // 0 * 2 = 0
    static int base = 100;
    static int calc() { return base * 2; }
}
💼 In the real world

The lazy holder singleton

Lazy init powers a classic idiom: put the instance in a nested Holder class. It's created only when get() first touches Holder, and the JVM guarantees initialization runs once even under thread races, with no synchronized needed. Old JDBC code used Class.forName("...Driver") just to run a driver's static block.

Key takeaways

  1. Triggers: new, static method call, non-constant static field, Class.forName
  2. Not triggers: Foo.class, new Foo[10], compile-time constants
  3. A superclass is initialized before its subclass
  4. Static initializers run once, in textual order

💡 A class is like a shop that opens its doors only when the first customer walks in, not when it appears on the map.

🤯 Did you know?

The JVM guarantees a class's static initialization runs exactly once, even when many threads race to use it first. Other threads simply wait until it finishes.

Practice questions

What does this print?

class A {
    static { System.out.println("A init"); }
    static final int X = 5;
    static int y = 7;
}
void main() {
    System.out.println(A.X);
    System.out.println("--");
    System.out.println(A.y);
}
  1. A init 5 -- A init 7
  2. 5 -- 7
  3. A init 5 -- 7
  4. 5 -- A init 7
Check your answer

5 -- A init 7. A.X is a compile-time constant, so javac copies 5 into main and A is not touched. Reading A.y is an active use, so A is initialized right before 7 is printed.

What does this print?

class P {
    static int v = 1;
    static { System.out.println("P init"); }
}
class Ch extends P {
    static { System.out.println("Ch init"); }
}
void main() {
    System.out.println(Ch.v);
}
  1. P init 1
  2. 1
  3. P init Ch init 1
  4. Ch init P init 1
Check your answer

P init 1. v is declared in P, so Ch.v is really an access to P.v. Only P is initialized; Ch's static block never runs.

Once a class is alive, where do its objects, its locals and its metadata live? Next: a tour of the JVM's memory areas.