What Happens When a Java Program Runs
You typed two commands, javac and java, and a line of text appeared. A lot happened in between. Knowing the main steps helps you understand error messages: some mistakes are caught when you compile, others only when the program runs, and the step tells you where to look.
Two Stages: Compile Time and Runtime
Every Java program goes through two separate stages.
| Stage | Started by | What happens | Mistakes found here |
|---|---|---|---|
| Compile time | javac Fare.java | Your code is checked and translated into bytecode in Fare.class | Typing and syntax mistakes: a missing ;, a wrong type, a misspelled name |
| Runtime | java Fare | The JVM loads the bytecode and runs it | Problems that depend on running: dividing by zero, a missing file, reading past the end of an array |
At Compile Time: What javac Does
When you run javac Fare.java, the compiler:
- Reads your source code and splits it into words and symbols.
- Checks the grammar: braces match, statements end with
;, keywords are spelt right. - Checks the meaning: every variable exists, every value has the right type, every method you call is real.
- Writes bytecode to
Fare.class: compact instructions for the JVM, not for any one processor.
If any check fails, javac prints an error with the file name and line number, and stops.
At Runtime: What the JVM Does
When you run java Fare, the java launcher starts a JVM, and the JVM:
- Loads the class. The class loader finds
Fare.classand reads it into memory. Classes you use later, such asStringorArrayList, are loaded when they are first needed. - Verifies the bytecode. The bytecode verifier checks that the instructions are safe: no jumping into the middle of nowhere, no treating a number as an object. This protects the JVM from damaged or tampered class files.
- Finds
mainand runs it. The execution engine starts by interpreting the bytecode, one instruction at a time. - Compiles hot code. The JIT (just-in-time) compiler watches which methods and loops run most often and turns them into fast machine code for your processor.
- Cleans up memory. While the program runs, the garbage collector frees objects that are no longer used.
See It Step by Step
This program does a little work so there's something for each stage to do.
public class Fare {
public static void main(String[] args) {
int farePerPerson = 450;
int passengers = 3;
int total = farePerPerson * passengers;
System.out.println(total);
}
}1350How it works
- At compile time,
javacchecks thatfarePerPerson,passengersandtotalare declared asintbefore they're used, and that*makes sense for twointvalues. - It writes
Fare.classwith bytecode for the multiplication and the print. - At runtime, the class loader loads
Fare, the verifier checks it, and the interpreter runsmain. - Line 6 needs
SystemandPrintStream, so those classes are loaded too, and1350is printed.
Try it yourself: change line 4 to int passengers = "three";. Does the mistake appear when you compile, or when you run?
Common Mistakes
Thinking javac runs the program. javac only checks and translates. Nothing runs until you use java.
Thinking the JVM runs your .java file. The JVM only understands bytecode. Even java Fare.java compiles the file in memory first, then runs the bytecode.
Expecting every error at compile time. The compiler can't know that a file will be missing or that a user will type a letter instead of a number. Those problems show up as exceptions at runtime, which a later lesson teaches you to handle.
Under the Hood
You can look at the bytecode yourself with the javap tool from the JDK. Compile first, then disassemble:
javac Fare.java
javap -c FarePart of the output looks like this (here for a version of Fare that multiplies 450 * 3 directly):
public static void main(java.lang.String[]);
Code:
0: sipush 1350
3: istore_1
4: getstatic #7 // Field java/lang/System.out
7: iload_1
8: invokevirtual #13 // Method java/io/PrintStream.println:(I)V
11: returnTwo things to notice:
javacalready worked out450 * 3 = 1350at compile time, because both numbers were fixed. This is called constant folding.- The instructions are simple: push a number, store it, load it, call
println. That's the language the JVM understands. A later lesson on bytecode goes into detail.
Interview Corner
What happens when you compile and run a Java program?
javac checks the source code and compiles it into bytecode in a .class file. When you run it with java, the JVM's class loader loads the class, the bytecode verifier checks it, and the execution engine runs it, interpreting at first and using the JIT compiler to turn frequently used code into machine code. The garbage collector frees unused memory while it runs.
What is the difference between a compile-time error and a runtime error?
A compile-time error is found by javac before the program runs, such as a missing semicolon or a type mismatch, and no .class file is produced. A runtime error happens while the program runs, such as dividing by zero, and usually appears as an exception.
Practice
- Easy: For each mistake, say whether it is found at compile time or at runtime: a missing
;, dividing a number by zero, a misspelled variable name, opening a file that doesn't exist. - Harder: Compile
Fare.javaand runjavap -c Fare. Find the instruction that multiplies the two numbers. Hint: look forimul. Why didn't the folded version above have it?
Key Takeaways
Was this tutorial helpful?