During compilation the compiler knows that B is dependant on A. It passes the same data to the JVM. During loading phase of class B, the JVM reads metadata sent by the compiler and finds out that B is dependant on A and hence loads (and runs static initializers) of A then continues to load B and then initializes B.
If we had 2 classes Sample and Test extends Sample the Class Constant Pool (part of byte code ) for Test would have :
Constant pool: #1 = Class #2 // Test #2 = Utf8 Test #3 = Class #4 // Sample <---- reference #4 = Utf8 Sample #5 = Utf8 <init> #6 = Utf8 ()V #7 = Utf8 Code #8 = Methodref #3.#9 // Sample."<init>":()V <-- init Sample #9 = NameAndType #5:#6 // "<init>":()V #10 = Utf8 LineNumberTable #11 = Utf8 LocalVariableTable #12 = Utf8 this #13 = Utf8 LTest; #14 = Utf8 main #15 = Utf8 ([Ljava/lang/String;)V #16 = Utf8 args #17 = Utf8 [Ljava/lang/String; #18 = Utf8 SourceFile #19 = Utf8 Sample.java
If you run java with verbose:class option then grep it, you will be able to see that the dependant class is being loaded.
java -verbose:class Test | grep 'Sample' [Loaded Sample from file:/Users/XXXX/Workspaces/SampleTest/Sample/bin/] <== Sample loaded first because test depends on Sample. [Loaded Test from file:/Users/XXXX/Workspaces/SampleTest/Sample/bin/]