This page covers what happens when the compiled program runs: how it starts, when classes are loaded and initialised, the order of steps when an object is created, and how it ends (JLS chapter 12). It also lists which changes to source keep code compiled against the old version working (JLS chapter 13).
Start-up and main
The JVM starts by loading, linking and initialising a main class chosen by
the launcher (java com.example.App, the Main-Class of a JAR,
or the first class of a source file run with java App.java), then calls its
main method (JLS 12.1).
A candidate main method is a method named main that is not
private, returns void, and has either one parameter of type
String[] (or String...) or no parameters (JLS 12.1.4,
Java 25):
| Form | Allowed |
|---|---|
public static void main(String[] args) | Every version |
static void main(String[] args), static void main() | Java 25 |
void main(String[] args), void main() (instance methods) | Java 25 |
- If the class declares or inherits a candidate with a
String[]parameter, that one is used; otherwise the one with no parameters. - For an instance
main, the launcher creates an object of the class with its constructor without parameters, which must not be private, and callsmainon it. - Before Java 25, only
public static void main(String[])was recognised (from Java 21 to 24 the other forms were a preview feature).
Loading and linking
| Step | What happens |
|---|---|
| Loading | A class loader finds the binary form, normally a .class file, and creates a Class object. Superclasses and superinterfaces are loaded first |
| Verification | The bytecode is checked to be well formed and type-safe |
| Preparation | Static fields are created and set to their default values |
| Resolution | Symbolic references to other classes, fields and methods are looked up, now or lazily when first used; failures throw subclasses of LinkageError such as NoSuchMethodError |
A class is identified at run time by its binary name together with its defining class loader, so the same class file loaded by two loaders gives two distinct classes.
Class initialisation
Initialising a class runs its static initialisers and static field initialisers, in the order they appear in the source. A class is initialised just before the first of these (JLS 12.4.1):
- an instance of it is created;
- one of its static methods is called;
- one of its static fields is assigned, or used when it is not a constant variable;
- reflection asks for it, as with
Class.forName("…"); - a subclass is initialised, or it is the main class.
Before a class is initialised, its superclass is, and so are those of its
superinterfaces that declare default methods. Using a constant variable, such as
Integer.MAX_VALUE, does not initialise its class, since the value was
copied in at compile time. Neither does using a class literal or declaring a
variable of the type.
- Initialisation is thread-safe: the JVM locks the class so that only one thread runs the initialisers, and others wait. This makes the holder class idiom a safe lazy singleton.
- If an initialiser throws, the exception is wrapped in
ExceptionInInitializerError(unless it is already anError), and the class is marked erroneous: every later attempt to use it throwsNoClassDefFoundError. - A cycle of classes that use each other while initialising can see default values: a thread that is already initialising a class may use it before it is finished.
class Config {
private Config() { }
private static class Holder { // initialised on first call of get()
static final Config INSTANCE = new Config();
}
static Config get() { return Holder.INSTANCE; }
}
Object creation
For new C(args), the steps are (JLS 12.5):
- Memory is allocated for all fields of
Cand its superclasses, set to their default values. If there is no memory,OutOfMemoryErroris thrown. - The arguments are evaluated and the chosen constructor is entered.
- If the constructor starts with, or contains after a prologue,
this(…), its prologue runs and then the other constructor runs these steps from step 2; afterwards the epilogue runs and creation is complete. - Otherwise the prologue runs (Java 25: statements before
super(…)), then the superclass constructor, explicit or implicit, runs these steps for the superclass, up toObject. - The instance initialisers and instance field initialisers of
Crun, in textual order. - The rest of the constructor body (the epilogue) runs.
An overridden method called from a superclass constructor runs before the
subclass’s field initialisers, and sees their default values. Since Java 25 a subclass
can avoid this by assigning its fields in the prologue, before
super(…).
class Base {
Base() { describe(); }
void describe() { }
}
class Derived extends Base {
String name = "derived";
@Override void describe() { System.out.println(name); }
}
new Derived(); // prints "null": name is assigned after Base() returns
Finalisation and unloading
Object.finalize() was called by the garbage collector before an object
was reclaimed (JLS 12.6). Finalisation has been deprecated for removal since Java 18
(JEP 421), and can be turned off with --finalization=disabled; use
try-with-resources and java.lang.ref.Cleaner instead. A class or
interface may be unloaded when its defining class loader is unreachable; classes
loaded by the bootstrap loader are never unloaded (JLS 12.7).
Program exit
A program exits when every thread that is not a daemon thread has ended, or when
Runtime.exit (System.exit) is called (JLS 12.8). The exit
status is the value passed to exit, or 0 if the threads ended normally;
the java launcher returns 1 when main ends with an uncaught
exception. Shutdown hooks run on exit; Runtime.halt stops at once
without running them.
Binary compatibility
Classes are compiled separately, so a library may change after code that uses it was compiled. A change is binary compatible if code compiled against the old version still links and runs without recompiling (JLS chapter 13). Some common cases:
| Change | Binary compatible? |
|---|---|
| Adding a class, field, method or constructor | Yes (but a new method may clash with one a subclass already declares) |
| Adding a default method to an interface | Yes |
| Adding an abstract method to an interface or abstract class | Links, but calling it on an old implementation throws AbstractMethodError |
| Removing or renaming a public or protected member | No: NoSuchFieldError or NoSuchMethodError |
| Changing a method’s parameter or return types | No: the descriptor is part of the call site |
| Changing a member between static and instance | No: IncompatibleClassChangeError |
Making a class final that has subclasses, or a method final that is overridden | No: VerifyError or IncompatibleClassChangeError |
Making a class abstract | No, for code that creates instances: InstantiationError |
| Changing the value of a constant variable | Links, but old code keeps the old value, which was copied in at compile time |
| Adding an enum constant | Yes, but old exhaustive switches meet a value they did not list |
| Reordering fields or methods, adding private members | Yes |