1.2
JVM, JRE, JDK, and Your First Program
Build and run one Java file from the terminal, then follow it through the runtime.
Previously on
Section 1.1 established the route from source code to a platform:
source -> bytecode -> JVM -> processor and operating systemThis lesson makes that route concrete. You will create one file, compile it, inspect the result, and run it without an IDE.
The problem
Java installation instructions use three similar names: JVM, JRE, and JDK. Mixing them up creates practical confusion.
Someone installs a runtime and expects to find a compiler. Another person passes a .class file to javac. An IDE hides both mistakes until a terminal or server exposes them.
We need one accurate model, then one complete trip through the tools.
The idea
The three names
- javac compiler
- java launcher
- inspection and debugging tools
- runtime modules
Install this when you develop Java programs.
- JVM
- standard runtime libraries
- supporting runtime files
The traditional package for running Java applications.
- class loading
- bytecode execution
- managed memory
The engine defined by the JVM specification and implemented by a runtime.
The conceptual nesting is still useful. Modern JDKs may not ship a separate folder or download named JRE.
Use these working definitions:
- The JVM executes class files under the JVM rules.
- A runtime environment adds the libraries and files applications need.
- The JDK adds tools used to build, inspect, and debug programs.
For this course, install a JDK. It contains what you need for both development and execution.
Confirm the installation
Open a terminal and run:
java -version
javac -versionThe first command checks the launcher. The second checks the compiler.
If java works but javac does not, you may have only a runtime on your command path. If neither works, the JDK may be missing or its bin directory may not be on PATH.
PATH is the list of directories your shell searches for commands. It is an operating-system feature, not a Java keyword.
Write a complete program
Create a plain text file named Demo.java:
public class Demo {
public static void main(String[] args) {
System.out.println("Hello, Java!");
}
}Every part has a job:
public class Demo declares a class named Demo
public static void main(...) declares the program entry method
String[] args receives command-line arguments
System.out.println(...) prints one lineYou do not need the full meaning of public, static, or String[] yet. Their later lessons will rebuild this line from known parts.
One rule matters now: a public top-level class named Demo belongs in Demo.java. Capitalisation must match.
Compile it
Change the terminal to the directory containing the file. Run:
javac Demo.javaIf compilation succeeds, the command usually prints nothing. It creates Demo.class in the same directory.
You now have two different artifacts:
| Demo.java | Demo.class | |
|---|---|---|
| Contains | Java source text | Class-file data and bytecode |
| Made for | Humans and javac | A compatible JVM |
| Typical action | Edit and compile | Load and execute |
Run it
Run the class through the Java launcher:
java DemoExpected output:
Hello, Java!javac accepts a file name, including .java. The java launcher accepts a binary class name, so you write Demo without an extension.
Under the hood
Going deeperCompile-time errors and run-time errors are different
Remove the semicolon after println, then run javac Demo.java. The compiler reports a source error and does not produce a new valid class file.
Restore the semicolon, compile, then run java Missing. The launcher cannot find a class with that name.
These failures occur at different stages:
Find the failing stage
- Source parsing and type checkingjavac reports invalid Java source.
- Class lookupThe launcher reports that the requested class cannot be found.
- Entry-point lookupThe class exists, but no suitable main method exists.
- Program executionThe program starts and may then throw an exception.
Reading the stage is faster than treating every red message as the same problem.
The class path answers “where?”
By default, java Demo searches the current directory for Demo.class. That search location is part of the class path.
You can state the current directory explicitly:
java -cp . Demo-cp means class path. The dot means the current directory.
Packages later add directory structure and qualified names. The same lookup rule remains: the runtime needs both the class name and a place to search.
What the runtime does with the class
Before your first println executes, the runtime performs several jobs.
- Loading: locate the bytes for
Demoand create the JVM’s in-memory representation. - Verification: check structural and bytecode safety rules.
- Linking: prepare class data and resolve required symbolic references.
- Initialization: run required static initialization for the class.
- Invocation: call the valid
mainmethod.
That is why java Demo does more than open a file and read instructions from the first byte.
Interpreting and compiling at run time
javac is an ahead-of-time compiler from Java source to JVM bytecode. That makes Java a compiled language.
A common JVM can also interpret bytecode and compile selected methods into native code while the program runs. The second compiler is called a just-in-time compiler, or JIT compiler.
Runtime profiling lets the JVM spend optimisation effort where the program actually spends time. Code that rarely runs may not need expensive compilation.
These are implementation strategies. The JVM specification defines observable behaviour, not one mandatory balance between interpretation and JIT compilation.
You can inspect the class file without decoding raw bytes:
javap -c Demojavap is a JDK tool. The -c option prints readable bytecode instructions for each method.
SE, EE, and ME describe platforms and ecosystems
Java SE is the standard Java platform and core API used throughout this course.
Jakarta EE, historically called Java EE, adds specifications for enterprise server applications. It builds on Java SE rather than replacing it.
Java ME targets constrained and embedded environments. Its role is much smaller in modern general-purpose Java development.
These labels are not three different Java languages. They describe platform profiles, APIs, and deployment environments around the language.
What it costs
An IDE can compile and launch Java for you. That is useful after you understand the two commands it is running.
Learning the terminal route exposes file names, class names, stale class files, and class-path mistakes. Those details return in build tools, test runners, servers, and production diagnostics.
The small cost now prevents the development environment from becoming a black box.
Check yourself
Answer from memory before opening the explanation. Then compare your rule with the answer and revisit the example if they differ.
You want to write, compile, and run Java programs. Which package do you install?
Show the answer
Install a JDK. It provides the Java runtime, the
javaccompiler, and development tools. A JVM is the execution engine. JRE is the traditional name for a JVM plus runtime libraries and supporting files.Why does compilation use "javac Demo.java", while execution uses "java Demo"?
Show the answer
javaccompiles a source file, so it receives the file path. The launcher receives a binary class name. It searches the class path forDemo.class, loads that class, and calls its entry method.What happens before the first statement in main runs?
Show the answer
The launcher starts a JVM. The runtime locates and loads the class, checks its bytecode, links it, and initializes required class state. It then invokes a valid
mainmethod.
Exercises
Write each solution in VS Code, predict its result, and then run the checks. Reading an example does not replace implementing it.
5 exercises90 pointsabout 100 minutes
Prove You Have a JDK
ex-1-2-aGet Java working on your own machine, and then prove it.
Do not skip the proving part. “I installed it” is not the same as “it works”, and the difference between those two commands is the whole JRE versus JDK idea made real.
If something breaks, search for the error, read what people say, try things. Getting an install unstuck is a normal part of this job, and doing it once yourself is worth more than watching somebody else do it.
What your program must do
- Install JDK 21, which is the version used by this course's exercise checker
- Run both version commands and get a real version number from each
- Write down which JDK version you have
- Explain in one line why passing only the second command is not good enough
Sample run
java -version
javac -version
openjdk version "21.0.12"
javac 21.0.12
Hint 1
brew install openjdk), or your package manager on Linux, or winget on Windows. Search for it, try it, fix what breaks. That is a real skill.Hint 2
java -version works but javac -version says command not found, you have a JRE on your path but not a JDK. Or the JDK is installed but its bin folder is not on your PATH.Hint 3almost the answer
java is the launcher and javac is a JDK development tool. If only java works, your command path does not expose a complete JDK toolchain.Your First Program, the Slow Way
ex-1-2-bWrite, compile and run a Java program using nothing but a text editor and a terminal.
Do not use the Run button in VS Code or IntelliJ for this one. Use it for the rest of your life if you want, but do this one by hand.
The reason is simple. That green button runs javac and then java for you and hides both. If you have never seen the two steps separately, then compiling and running are one mysterious action in your head, and every error message from either one will look the same to you.
Do it once by hand and they become two clearly different things forever.
What your program must do
- Save the file as Demo.java, with a capital D
- Compile it from the terminal, not with a Run button
- Confirm that Demo.class appeared next to Demo.java
- Run it from the terminal and see the output
public class Demo {
public static void main(String[] args) {
System.out.println("Hello from bytecode");
}
}
Sample run
javac Demo.java
java Demo
Hello from bytecode
Hint 1
cd. On Windows, Command Prompt uses cd too.Hint 2
javac Demo.java nothing is printed. That is success. A compiler that has nothing to complain about says nothing. Check with ls or dir that Demo.class exists.Hint 3almost the answer
java Demo. Not java Demo.java, and not java Demo.class. The java command wants a class name, and it finds the file itself.Look at the Bytecode
ex-1-2-cYou have been told your .java file becomes bytecode. Now go and look at it.
Compile Demo.java from the previous exercise, then run javap on the result. First plain, then with -c to see the real instructions.
You are not expected to understand every line. You are expected to see, with your own eyes, that there is a readable middle language between your source code and the processor. That it is not zeros and ones. That it has names like ldc and invokevirtual.
Keep the output somewhere. You will come back to it twice: once in Phase V, once in Phase VII.
What to write
These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.
class Bytecode {
static String instructionThatLoadsAConstant()
static String instructionThatReadsAStaticField()
static String instructionThatCallsAMethodOnAnObject()
static String instructionThatCallsAStaticMethod()
static String instructionThatCallsAConstructor()
static String instructionThatEndsAMethod()
static boolean classFileHasAConstructorYouDidNotWrite()
static String classThatHiddenConstructorCalls()
static boolean bytecodeIsMachineCodeForYourProcessor()
static boolean classFileOnDiskIsBinary()
}Instruction names exactly as javap prints them, with no numbers and no hash marks. Capitals and surrounding spaces are ignored. For the class name, either Object or the full java/lang/Object is accepted. One answer needs you to add a static method to Demo and compile it again, because the sample output has no static call in it.
What your program must do
- Run javap on your compiled class, first plain and then with -c
- Find the instruction that pushes your text onto the stack
- Add a static method, recompile, and find the third kind of call
- Write down what the first block is, given that you never wrote a constructor
public class Bytecode {
// Compile Demo.java from the previous exercise, then run:
// javap Demo
// javap -c Demo
// Read the output and fill these in. Instruction names only, no numbers.
// TODO: which instruction holds your text?
static String instructionThatLoadsAConstant() { return "?"; }
// TODO: which one reads System.out?
static String instructionThatReadsAStaticField() { return "?"; }
// TODO: which one calls println on that object?
static String instructionThatCallsAMethodOnAnObject() { return "?"; }
// TODO: add a static method to Demo, call it, recompile, and look again
static String instructionThatCallsAStaticMethod() { return "?"; }
// TODO: look at the first block, the one named after the class itself
static String instructionThatCallsAConstructor() { return "?"; }
// TODO: which instruction ends every method in the output?
static String instructionThatEndsAMethod() { return "?"; }
// ---- the thing at the top you did not write ----
// TODO
static boolean classFileHasAConstructorYouDidNotWrite() { return false; }
// TODO: which class does it call into? Write down the answer and move on.
static String classThatHiddenConstructorCalls() { return "?"; }
// ---- what this tells you ----
// TODO: is bytecode machine code for the processor in your laptop?
static boolean bytecodeIsMachineCodeForYourProcessor() { return true; }
// TODO: open Demo.class in a text editor. Is the file itself readable?
static boolean classFileOnDiskIsBinary() { return false; }
public static void main(String[] args) {
System.out.println("load a constant : " + instructionThatLoadsAConstant());
System.out.println("read a static field : " + instructionThatReadsAStaticField());
System.out.println("call on an object : " + instructionThatCallsAMethodOnAnObject());
}
}
Sample run
javap -c Demo
public class Demo {
public Demo();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #7 // Field java/lang/System.out
3: ldc #13 // String Hello from bytecode
5: invokevirtual #15 // Method java/io/PrintStream.println
8: return
}
Hint 1
javap comes with your JDK, so you already have it. Run it in the same folder as Demo.class, and give it the class name with no extension: javap -c Demo.Hint 2
ldc is short for load constant. getstatic reads a static field. The three call instructions all begin with invoke, and which one appears depends on whether there is an object involved and whether anything has to be looked up.Hint 3almost the answer
java/lang/Object. Do not chase this now. Write it down. In Section 8.3 you will find out why every class you write already extends Object, and this output is the proof.Break It On Purpose
ex-1-2-dDeliberately breaking things is how you learn what error messages mean. Right now you have a working program, which makes this the perfect time.
Break it four ways. After each one, fix it before moving on.
- Delete a semicolon and compile.
- Rename the class inside the file to
Hello, but leave the file namedDemo.java. Compile. - Change
maintomian, compile it (it will compile fine), then run it. - Compile normally, then delete
Demo.class, then runjava Demo.
Number 3 is the interesting one. Ask yourself why the compiler was happy with a method called mian and only the JVM complained.
What to write
These exact signatures. The checks call them by name, so a different name or a different parameter type will not build.
class Break {
static String whoReported(String message)
static String whoReportedMissingSemicolon()
static String whoReportedFilenameMismatch()
static String whoReportedMisspeltMain()
static String whoReportedMissingClassFile()
static boolean compilerChecksThatMainExists()
static boolean javacMessagesNameASourceLine()
static boolean jvmMessagesNameASourceLine()
}Every answer is "javac" or "jvm", and capitals and surrounding spaces are ignored. whoReported must be a rule rather than a list of the four messages, because the tests feed it errors you have not caused. One of those is a stack trace, which does print a line number and is still the JVM talking, so look closely at what surrounds the line number in a real compiler error.
What your program must do
- Cause all four failures deliberately and copy the exact message
- Say whether javac or the JVM produced each one
- Write whoReported as a rule that works on messages you have not seen
- Explain why javac was happy with a method called mian
public class Break {
// TODO: the rule, worked out from the four messages you collected.
// Return "javac" or "jvm".
static String whoReported(String message) {
return "?";
}
// ---- the four failures you caused ----
// Cause each one, copy the exact message, then say who produced it.
// TODO: 1. delete a semicolon and compile
static String whoReportedMissingSemicolon() { return "?"; }
// TODO: 2. rename the class inside Demo.java to Hello, and compile
static String whoReportedFilenameMismatch() { return "?"; }
// TODO: 3. change main to mian, compile (it will work), then run
static String whoReportedMisspeltMain() { return "?"; }
// TODO: 4. compile, delete Demo.class, then run java Demo
static String whoReportedMissingClassFile() { return "?"; }
// ---- what that tells you ----
// TODO: number 3 compiled. Does javac check that a main method exists?
static boolean compilerChecksThatMainExists() { return true; }
// TODO: what does a javac message always contain that a JVM message never does?
static boolean javacMessagesNameASourceLine() { return false; }
static boolean jvmMessagesNameASourceLine() { return true; }
public static void main(String[] args) {
// Paste your four real messages in here and check your rule against them.
String[] messages = {
"TODO",
};
for (String m : messages) {
System.out.println(whoReported(m) + " <- " + m);
}
}
}
Hint 1
Hint 2
Demo.java:3: error: ';' expected. Three parts: the file, the line, then the word error. A JVM message has none of that shape, because the JVM never opened your source file.Hint 3almost the answer
at Demo.main(Demo.java:4) contains a file and a line number and is still the JVM talking. The part only the compiler writes is the : error: straight after the line number.Compare Tiered Execution With Interpreter-Only Mode
ex-1-2-eThis section explained that a JVM may interpret code and compile active methods while the program runs. This exercise observes one implementation of that strategy.
The starter repeats one method and prints the duration of each round. Run it several times normally, then repeat with interpreter-only mode.
Do not require a dramatic cliff. The JVM may compile during an early call, and the operating system adds noise. Use compilation logs when supported and explain what the experiment can and cannot prove.
What your program must do
- Run the program three times and record all 20 rounds from each run
- Run it three times with -Xint and record the same data
- If your JVM supports it, use -XX:+PrintCompilation to observe compilation events
- Explain the difference and name two reasons the timings are noisy
public class Warmup {
// Some work that is worth compiling. Deliberately simple.
static long work(int n) {
long total = 0;
for (int i = 0; i < n; i++) {
total += i % 7;
}
return total;
}
public static void main(String[] args) {
for (int round = 1; round <= 20; round++) {
long start = System.nanoTime();
long result = work(2_000_000);
long took = (System.nanoTime() - start) / 1_000_000;
System.out.println("round " + round + ": " + took + " ms (result " + result + ")");
}
}
}
Sample run
round 1: 14 ms
round 2: 9 ms
round 3: 8 ms
round 4: 2 ms
round 5: 1 ms
...
round 20: 1 ms
Hint 1
java Warmup. Early rounds may be slower, but a sharp drop is not guaranteed. Compilation can begin during the first measured call.Hint 2
java -Xint Warmup. The -Xint flag tells the JVM to use only the interpreter and never the JIT compiler. Compare the two sets of numbers.Hint 3almost the answer
java -XX:+PrintCompilation Warmup prints compilation events. Other JVMs may use different flags. Scheduling, CPU frequency, background work, timer resolution, and JIT activity all affect timings. Compare repeated runs and describe a trend rather than claiming an exact threshold.After the credits
You have completed the first full Java toolchain cycle:
Demo.java -> javac -> Demo.class -> java -> class loading -> mainThe next phase begins with the values that move through those bytecode instructions.
Threads you opened in this section
- BytecodeOverloading is decided here, at compile time, not while running.5.1 - Methods, Calls, Recursion, and Overloading
- BytecodeOverriding is the opposite: bytecode leaves the choice for later.7.2 - Abstraction, Polymorphism, Abstract Classes, and Interfaces
- BytecodeThe Method Area is where this bytecode lives while your program runs.Phase XIII. Memory and the Garbage Collector
- JVMHeaders, field layout, references, and alignment depend on the JVM configuration.6.3 - Objects in Memory: Size, Copying, Passing
- JVMA full tour of every memory area the JVM keeps.Phase XIII. Memory and the Garbage Collector
- JVMThreads are the JVM handing work to your CPU cores.Phase XIV. Concurrency
JVM will return in 4.2 - How Arrays Work