The Lifecycle of a Java Program Explained: From Source Code to JVM Execution
Discover how a Java program runs from source code to execution inside the JVM. Learn about compilation, bytecode, class loading, and more.
The LifeCycle of a Java Program
If we asked developers across the world, the programming language of their choosing, almost 25% of them would probably reply, Java. Even after 30 years since it first arrived, Java still remains one of the most popular programming languages capable of powering modern web applications. What makes Java so capable, is already talked about in one of our other articles, and in this one, we are mainly going to be focussing on the “LifeCycle of a Java Program”.
We will try to understand what happens behind the scenes within your JVM or Java Virtual Machine, and we will try to answer how Java promises engineers a “Write Once, Run Anywhere” code. So, let’s get started.
The LifeCycle of a Java Program
If you are running short on time, or you want to gain a fundamental undertstanding of the LifeCycle of a Java program, there are four primary steps in this lifecycle that you should know. When you press Run or Compile on your local IDE, the Java framework executes 4 Steps, and these 4 steps together, form the LifeCycle of a Java program.

It begins with the Source code. Developers create a Java source code file, implement OOPs, Methods, to write code and build applications, and then save these files with a “.java” extension. Once you are satisfied with your code, we move on to the next step, “Compilation”.
In the second step, the source code is feeded to the compiler. Behind the scenes, the JDK or Java Development Kit makes use of javac commands to compile the source code, and validate it for syntax error(s), if any. If no errors are found, the code compiles successfully, the JDK creates a file with a “.class” extension, and this is the third step in the Java program lifecycle.
The file with the “.class” extension contains “bytecode”, and this is where the concept of “Write Once, Run Anywhere” comes into play. You see, regardless of which machine you make use of to write the code, the format of bytecode never differs i.e. the format of ByteCode remains consistent regardless of machine/system. So, you can use a Windows OS or you can use a MacOS for coding, the Bytecode generated from both these machines will be identical.
It is the job of the JVM or Java Virtual Machine on your system/OS to read the Bytecode, and translate the code, so that it can execute on your local machine. The translation of ByteCode to -> locally executable code is the fourth step in the LifeCycle of a Java program.
To sum it all up, the lifecycle of a Java program cab be summed up in four core steps, with these being
- Writing the source code. Using “.java” extension to write code.
- Compilation. Javac commands to compile the source code, validate for syntax errors, etc.
- Generating Bytecode. Once the code compiles, a “.class” file is generated which contains ByteCode.
- Execution by the JVM. The JVM is responsible for converting bytecode into machine-level instructions that your system can execute.
It is important to note that these four steps are the core pills of a Java program’s lifecycle, and behind the scenes, the Java Virtual Machine performs a number of steps of its own, before the code can be executed on your system.
The next section, tries to take a deeper dive into the lifecycle of a Java program, and tries to break down each step along with what happens inside the JVM.
Breaking Down Each Stage of the Java Program Lifecycle

Step 1 — Writing the Source Code
Before we can execute a Java program, we need to write the code which will be executed. In the first step, developers use the Java Development Kit, their favorite IDE, and their love for Java to write human-readable code, that executes a certain logic. Once you are satisfied with your block of code, you hit Compile/Execute.

But the Java Virtual Machine, it doesn’t understand what a for loop is? All it understands are 0s and 1s.
Step 2 — Compiling the Source Code
When you hit compile or run javac ProgramName.java, the compiler from the Java Development Kit performs multiple checks and transformations. If errors are found, compilation stops, the errors are shown in the terminal, and you need to fix these errors before running the program.

If no errors are found, a “.class” file is created. This .class file contains ByteCode, a JVM-readable platform-independent code which the JVM can execute.
Step 3 — ByteCode Execution Phase
Once the compiler generates the ByteCode, the JVM reads it, and executes the program.
The Java Virtual Machine (JVM) is responsible for loading Java bytecode, allocating memory, and translating that bytecode into platform-specific machine instructions. Because every processor architecture (such as x86, ARM, MIPS, or PowerPC) operates with its own instruction set, the JVM acts as an abstraction layer that ensures Java programs can run on any system without modification.
Beyond execution, the JVM delivers essential runtime services, including automatic memory management, thread coordination, and structured exception handling.
Loading — Bringing Classes Into Memory
Loading refers to the process of finding the binary form of a class or interface (i.e., a class file format) with a particular name and constructing a Class object from that binary form.
The JVM uses a ClassLoader to find the binary representation of Main. The ClassLoader class and its subclasses implement the loading process. The method defineClass is called to construct Class objects from binary representation of the class file format.
The JVM doesn’t load everything at once — it loads classes on demand.
Types of Class Loaders
- Bootstrap: loads the core Java classes from the
rt.jarfile. - Extension: loads classes from the
extdirectory. - Application: load classes from other locations, such as the classpath or a remote server.

Custom class loaders are often used in frameworks, servers, or plugin systems where classes must be loaded dynamically from unusual sources.
In summary, the class loading process performs these three functions:
- Create a binary stream of data from the class file
- Parse the binary data according to the internal data structure
- Create an instance of
java.lang.Class
When this is done, the class instance is ready for linking.
Linking — Preparing Classes for Execution
Linking refers to the process of taking a binary form of a class or interface and combining it into the runtime state of the JVM, so that it can be executed. Linking involves three steps: verification of the binary representation, preparation of a class of interface, and (optionally) resolution of symbolic references.
- Verification: checks that the loaded representation of a class is well-formed, with a proper symbol table. It also checks that the code that implements the class obeys the semantic requirements of the Java programming languague and the JVM. For example, it checks that every instruction has a valid operation code; that every branch instruction branches to the start of some other instruction, rather than into the middle of an instruction; that every method has a correct signature.
- Preparation: involves creating the
staticfields (class variables and constants) for a class or interface and initializings such fields to the default values. This involves allocation of static storage and any data structures that are used internally by the implementation of the JVM, such as method tables. - Resolution: is the process of checking symbolic references from a class to other classes and interfaces, by loading the other classes and interfaces that are mentioned, and checking that the references are correct.
In summary, the linking process involves three phases:
- Verification
- Preparation
- Resolution (optional)
When this is done, the classes are ready for initialization.
Initialization — Executing Static Code
Initialization is when a class truly becomes active.
During this stage:
- Static variables receive their assigned values
- Static blocks execute
- Superclasses initialize before subclasses
Initialization of a class consists of executing its static initializers and the initializers for static fields (class variables) declared in the class. The static initializers are executed in the order that they appear in the source code.
class Main {
static int x = 1;
static int y;
static {
y = x + 1;
}
static int z = x + y;
}
The JVM runs these initializations in the order they appear in the source code.
A class initializes when:
- An instance is created
- A static method is called
- A static field is assigned
- A non-constant static field is accessed
- Reflection forces loading
Only after initialization can main() execute.
Instantiation — Creating Objects
A new class instance is explicitly created when evaluation of a class instance creation expression is performed (e.g., when using the new operator).
A class instance may be implicitly created when:
- Loading a class or interface that contains a string literal or a text block may create a new
Stringobject - Execution of an operation that causes boxing conversion may create a new object of a wrapper class
- Execution of a string concatenation operation may create a new
Stringobject - Evaluation of a method reference expression or a lambda expression may create a new object of a functional interface.
Here’s an example of creating a new instance of the class Student:
Student student1 = new Student();
During instantiation, the following steps are performed:
- Memory is allocated on the heap to hold the new object
- The class’s constructor is called to initialize the new object
- The reference to the new object is returned
Garbage Collection — Automatic Memory Cleanup
Java automatically frees memory occupied by objects that are no longer reachable. This removes the need for manual memory management and helps prevent leaks.
The JVM decides when to run GC — developers can suggest it (System.gc()), but cannot force it.
Class Unloading — Removing Classes from Memory
Unloading refers to the process of removing a class or interface from the runtime state of the JVM (e.g., when its defining class loader is reclaimed by the garbage collector). Class unloading reduces memory use. Consequently, this optimization is only significant for applications that load large numbers of classes and interfaces, and that stop to use them after some time.
Classes can be removed from memory when their class loader becomes unreachable and is garbage collected.
This is more common in:
- Application servers
- Plugin systems
- Dynamic module loading environments
Classes and interfaces loaded by the bootstrap class loader are never unloaded. Therefore, in typical standalone applications, class unloading is less common because the system ClassLoader is usually active for the lifetime of the application, and hence the classes it loads are not unloaded.
Different from garbage collection, class unloading refers to the removal of a class definition (the Class object representing the LargeClass, in this case) and its associated metadata from the JVM’s memory. This typically happens when the class loader that loaded the class becomes eligible for garbage collection, as previously explained. Class unloading depends on several factors, such as the behavior of the garbage collector and the JVM’s implementation details.
Program Exit — JVM Shutdown
The program ends when:
- All non-daemon threads finish
System.exit()is called
Program exit refers to the process of terminating the execution of a program. This means that all threads that are not daemon threads are terminated, or some thread invokes the exit method of the Runtime class. This method halts the JVM and exit with a specified exit code. However, the use of this method is restricted by a security manager. If a security manager is present and it does not allow the program to exit, the exit method will throw a SecurityException.
Conclusion
The lifecycle of a Java program is far more than just writing code and pressing Run. Behind every successful execution lies a carefully engineered sequence of stages — compilation into bytecode, intelligent class loading, rigorous verification, controlled initialization, optimized execution, and automatic memory management.
What makes this process truly powerful is how much work the JVM does on behalf of the developer. It ensures security through bytecode verification, portability through platform-independent bytecode, performance through JIT compilation, and stability through garbage collection. All of this happens silently in the background, allowing developers to focus on solving real problems instead of worrying about memory, hardware differences, or low-level system behavior.
Understanding this lifecycle doesn’t just make you a better Java programmer — it helps you think like the JVM. And when you understand how the runtime thinks, you can write more efficient code, debug smarter, and perform better in technical interviews.
Java’s promise of “Write Once, Run Anywhere” isn’t just a slogan. It’s the result of a sophisticated execution model that has stood the test of time — and that’s exactly why Java continues to power the world’s most critical systems decades after its creation.
메타데이터
- post_id
- ff89d9ff59cb
- slug
- the-lifecycle-of-a-java-program-explained-from-source-code-to-jvm-execution-ff89d9ff59cb
- url
- https://medium.com/@kunal.resolute/the-lifecycle-of-a-java-program-explained-from-source-code-to-jvm-execution-ff89d9ff59cb
- canonical_url
- https://medium.com/@kunal.resolute/the-lifecycle-of-a-java-program-explained-from-source-code-to-jvm-execution-ff89d9ff59cb
- author_url
- https://medium.com/@kunal.resolute
- status
- ok
- fetched_at
- 2026-09-10 22:52:05