How Programs Execute
From source code to a running program — compilation, classloading, and the run cycle in Java
So far we have written .java files and watched them produce
output. Magic. This page replaces that magic with a mental model of
what actually happens between "type the code" and "see the
output."
The four stages
Every Java program goes through these four stages:
- Source code. You write
Main.java— text that humans can read. - Compilation. A program called
javacreads your source and produces aMain.classfile containing bytecode — a compact, abstract instruction set the JVM understands. - Bytecode. This is not machine code for your CPU. It's an
intermediate format. The same
.classfile works on Windows, macOS, Linux, ARM, x86 — anywhere a JVM is installed. - Execution. The JVM (started by the
javacommand) loads the bytecode, verifies it, translates it (lazily) into actual CPU instructions, and runs them.
This separation between compile and run is exactly what makes "write once, run anywhere" possible. The compiler doesn't need to know what kind of CPU will eventually run the code. The JVM, on each platform, does that part.
What does javac actually do?
javac is a program written in Java itself. It:
- Reads the source text.
- Parses it into an internal tree (an AST — abstract syntax tree).
- Checks types, scopes, access modifiers — "does
x.foo()make sense given the type ofx?" - Reports errors if anything is wrong (and produces no
.classif there are errors). - Emits one
.classfile per class, containing bytecode plus metadata.
If javac fails, your program never runs at all. The compiler is
your first and best safety net.
What does the java command actually do?
The java command starts the JVM and asks it to run the main
method of a class you name. The JVM then:
Three pieces are worth understanding even at a beginner level:
- Classloader. Finds
.classfiles on disk (or in JAR archives), reads them into memory, links them together. Loads only what is actually used —Cat.classmay never load if your program never mentionsCat. - Verifier. Checks that the bytecode is internally consistent
("does this method really claim to leave one
inton the stack? does this branch jump to a valid instruction?"). This protects the JVM from broken or malicious class files. - Interpreter + JIT. The JVM first interprets bytecode instruction-by-instruction. When it notices a method being called often (a "hot" method), the Just-In-Time compiler (JIT) turns that method into real CPU instructions, so subsequent calls run at native speed. We will cover this more on the next page.
A worked example
Let's walk a tiny program through every stage.
When you click Run:
- The browser-based Java runtime invokes the equivalent of
javaconMain.java, producingMain.class(bytecode). - The runtime then invokes the equivalent of
java Main, which starts a JVM, loadsMain, and runsmain. mainallocates twointlocals (x,y) on its stack frame, callsadd, which gets its own frame, returns 5, prints.
The output appears. Behind that one click are all the stages above.
"Compile-time" vs "run-time"
You will hear these phrases all the time. They simply describe when something is decided:
- Compile-time: decided while
javacis running. Examples:- "What is the type of
x?" - "Does
FooextendBar?" - "Is this field accessible from here?"
- "What is the type of
- Run-time: decided while the JVM is running. Examples:
- "What value does
xhold right now?" - "Which override of
area()should we dispatch to?" - "Has this file actually been opened?"
- "What value does
Statically typed languages like Java try to push as many decisions as possible to compile-time, because compile-time errors are much cheaper than run-time crashes.
A note about IDEs and "running"
Modern IDEs (and online runners like this one) often appear to
"just run" your code. They are quietly performing the compile step
behind the scenes. There is no shortcut around .java → .class → run
— you just don't have to type it.
What does the Java compiler (javac) produce?
A native executable for your operating system
One or more .class files containing platform-independent bytecode
A new .java file
Machine code that only runs on the developer's CPU
Why does Java use bytecode and a JVM instead of compiling directly to native code?
Because native code is illegal
Because the JVM is faster than native execution
Because bytecode is portable — the same .class files can run on any platform that has a JVM, fulfilling Java's "write once, run anywhere" promise
Because Java cannot be compiled directly
Which of these is decided at compile time rather than at run time?
The actual numeric value of an int variable
Whether a particular field is accessible from a particular call site, based on its access modifier
Which override of a method is dispatched to
Whether a file actually exists on disk
Now that you have the rough shape of execution, the next page zooms into the JVM itself: what it really is, what it does, and the parts of it that are worth knowing as a beginner.