Have you ever wondered what happens behind the scenes when you press the “run” button on your Java program? The process involves a series of complex steps, from compiling and loading the code into memory to managing the data in data structures such as heap and stack.
Here, we’ll explore the steps involved in running a basic Java program, highlighting the roles of the loader, compiler, launcher, and memory model. Consider a simple Java program that calculates the factorial of the number “n” using recursion.
As you know, the factorial of a number is defined by the recurrence relation:
F (n) = n * F (n-1) with base cases F (0) = 1 and F (1) = 1
Let’s go through the various steps of compiling, loading and running this program and also understand the memory management aspect of this program.
Program example: Factorial calculation
public class Factorial
public static void main(String[] args)
int n = 5;
int result = factorial(n);
System.out.println("Factorial of " + n + " is: " + result);
public static int factorial(int n)
if (n == 0
1. Compiler: performance architect
Our journey begins with the compiler. Java Compiler (javac
) translates our readable Java code into bytecode that the JVM can understand.
When we publish javac Factorial.java
command, Java compiler (javac
) translates Factorial.java
source code to bytecode.
During compilation, the compiler checks for syntax errors and checks the code’s type safety, ensuring that data types are used correctly and consistently throughout the code. Any errors or inconsistencies are flagged, allowing the developer to modify the code.
2. Loader: Making the stage
With the stage set, it’s time for the loader, a vital component of the Java Virtual Machine (JVM), to take center stage. When starting a Java program, the loader retrieves the bytecode from the classpath, including Factorial
class. This bytecode is a platform-independent representation of our code.
In a simple Java program such as Factorial
for example, utility classes from the Java Standard Library, such as those in the java.lang
the package can be loaded into memory along with Factorial
class.
Some of the utility classes from java.lang
The package that can be loaded initially includes:
Object
Class: Every class in Java implicitly extendsObject
class, so that it is loaded into memory when the JVM starts. TheObject
class provides underlying methods such asequals
,hashCode
andtoString
.String
Class: TheString
class is often used in Java programs to manipulate strings. It is loaded into memory to support operations involving strings.System
Class: TheSystem
class provides access to system properties and I/O streams. Often used for console input/output, environment variables, and system-related operations.Math
Class: TheMath
class provides mathematical functions and constants. It is usually used for arithmetic calculations in Java programs.ClassLoader
Class: TheClassLoader
class is responsible for dynamically loading Java classes into the JVM. Although the program may not reference this class directly, it is included in the class loading process.
These utility classes are essential to the basic functioning of a Java program and are automatically loaded into memory by the JVM via the loader along with the Factorial
class. They provide the core functionality commonly used in Java applications.
3. Runner: Starting the action
As the program runs, the JVM coordinates execution by taking on the role of “runner”. The main method acts as an entry point, signaling the start of the action.
Memory management, exception handling, and method calls occur seamlessly.
- Objects and their instance variables are allocated on the heap. Method calls and local variables find their place on the stack.
- Each thread in the application has its own stack frame, providing private space for method calls and data storage.
- In the sample program, after calling the main method, a new stack frame appears on the stack, with local variables such as
n
andresult
. - The
factorial
a method is called which initiates a series of recursive calls and the creation of successive stack frames. - With each recursive call, the stack is filled with new frames, each of which encapsulates method parameters and local variables.
- As method calls return, the stack frames are smoothly retracted, passing control back to their predecessors.
- Finally, when the main method finishes running, the Java program terminates.
4. Model of memory: behind the scenes
Let’s go a little deeper into memory management to understand memory allocation.
Behind the scenes, the Java memory model governs how data is stored and accessed at runtime. The heap, a shared pool of memory, holds dynamically allocated objects, while the stack provides dedicated space for method calls and local variables.
During execution, memory plays a key role in the coordination of execution.
In our sample program, primitive variables like n
and result
find their place on the stack, within their respective stack frames.
As recursive calls to factoria
l method runs, stack frames are created and popped from the stack, reflecting the dynamic nature of method invocations.
Although there are none in our case, objects and their instance variables, if any, find their place on the heap.
But why are the objects assigned space in the pile, and why not on the stack? Let’s discuss it.
Objects are allocated on the heap as they are created at runtime and are dynamic. Objects created in Java can have different lifetimes specified using the scope identifiers “public”, “private”, “protected” and “private to package” (the default). This extends their lifetime beyond the scope of the methods or blocks in which they were created. Placing objects on the heap allows them to persist beyond a single method call, allowing them to be accessed from multiple parts of the program.
But references to those objects are usually stored on the stack or inside other objects on the heap.
This reference is essentially a memory address that points to the object’s location on the heap. So while the actual data about an object resides on the heap, a reference to that object is stored on the stack.
While object references are usually stored on the stack, it is important to note that objects can also be referenced directly from the heap. This happens when one object contains a reference to another object as one of its instance variables. In this case, the reference to the second object is stored within the memory allocated for the first object on the heap.
Here is an example to illustrate both scenarios:
public class Example
public static void main(String[] args)
// Creating an object and storing its reference on the stack
MyClass obj1 = new MyClass();
// Creating another object and storing its reference in the instance variable of the first object
obj1.setAnotherObject(new AnotherClass());
class MyClass
private AnotherClass anotherObject;
public void setAnotherObject(AnotherClass obj)
this.anotherObject = obj;
class AnotherClass
// Class definition
In this example:
When obj1
arises in main()
method, reference to MyClass
the object is stored on the stack.
When obj1.setAnotherObject(new AnotherClass())
it’s called, an AnotherClass
the object is created on the heap and its reference is stored inside MyClass
object in the stack.
So while objects are allocated on the heap, references to those objects are usually stored on the heap or within other objects on the heap, depending on the context in which they are used.
The seamless interaction between the heap and the heap ensures the efficient allocation and deallocation of memory, facilitating the seamless execution of our Java program.
Conclusion
As our journey through Java execution draws to a close, we reflect on the elaborate interaction of the loader, compiler, loader, and memory model that drives every Java program. From loading code into memory to managing data structures, the JVM ensures flawless execution of Java programs.
So the next time you run a Java program, take a moment to appreciate the magic behind the scenes that makes it all possible. And as the applause rings out, remember the journey from loader to memory model that paved the way for your code to shine.
Video