JVM Architecture 101: Get to Know Your Virtual Machine
Tags: java, jvm, bytecode
Published: 2018-05-06 08:50:36

---
title: JVM Architecture 101: Get to Know Your Virtual Machine
published: true
description: A beginner’s crash course in Java Virtual Machine (JVM) architecture and Java bytecode 101
tags: Java, JVM, Bytecode
---

Java applications are all around us, they’re on our phones, on our tablets, and on our computers. In many programming languages, this means compiling the code multiple times in order for it to run on different OSes. For us as developers, maybe the coolest thing about Java is that it’s designed to be platform-independent (as the old saying goes, “Write once, run anywhere”), so we only need to write and compile our code once.

How is this possible? Let’s dig into the Java Virtual Machine (JVM) to find out.

## The JVM Architecture

It may sound surprising, but the JVM itself knows nothing about the Java programming language. Instead, it knows how to execute its own instruction set, called **Java bytecode**, which is organized in binary **class files**. Java code is compiled by the _**javac**_ command into Java bytecode, which in turn gets translated into machine instructions by the JVM at runtime.

![](https://thepracticaldev.s3.amazonaws.com/i/qw4sirdsc3yjee1cz6lo.png)

#### Threads
Java is designed to be concurrent, which means that different calculations can be performed at the same time by running several threads within the same process. When a new JVM process starts, a new thread (called the _main thread_) is created within the JVM. From this _main thread_, the code starts to run and other threads can be spawned. Real applications can have thousands of running threads that serve different purposes. Some serve user requests, others execute asynchronous backend tasks, etc.

#### Stack and Frames
Each Java thread is created along with a frame stack designed to hold method frames and to control method invocation and return. A method frame is used to store data and partial calculations of the method to which it belongs. When the method returns, its frame is discarded. Then, its return value is passed back to the invoker frame that can now use it to complete its own calculation.

![alt text](https://thepracticaldev.s3.amazonaws.com/i/111wh4590t4fmcshxw3o.png "JVM Process Structure")

The JVM playground for executing a method is the method frame. The frame consists of two main parts:

1. **Local Variables Array** – where the method’s parameters and local variables are stored
2. **Operand Stack** – where the method’s computations are performed

![alt text](https://thepracticaldev.s3.amazonaws.com/i/089sf8hd7kussvk2m51u.png "Frame structure")

## How It Works
Let’s go over a simple example to understand how the different elements play together to run our program. Assume we have this simple program that calculates the value of 2+3 and prints the result:

```java
class SimpleExample {
	public static void main(String[] args) {
		int result = add(2,3);
		System.out.println(result);
	}

	public static int add(int a, int b) {
		return a+b;
	}
}
```

To compile this class we run _javac SimpleExample.java_, which results in the compiled file _SimpleExample.class_. We already know this is a binary file that contains bytecode. So how can we inspect the class bytecode? Using **javap**.

_javap_ is a command line tool that comes with the JDK and can disassemble class files. Calling _javap -c -p_ prints out the disassembled bytecode (-c) of the class, including private (-p) members and methods:

```
Compiled from "SimpleExample.java"
class SimpleExample {
  SimpleExample();
    Code:
       0: aload_0
       1: invokespecial #1                  // Method java/lang/Object."<init>":()V
       4: return

  public static void main(java.lang.String[]);
    Code:
       0: iconst_2
       1: iconst_3
       2: invokestatic  #2                  // Method add:(II)I
       5: istore_1
       6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
       9: iload_1
      10: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
      13: return

  public static int add(int, int);
    Code:
       0: iload_0
       1: iload_1
       2: iadd
       3: ireturn
}
```


Now what happens inside the JVM at runtime? _java SimpleExample_ starts a new JVM process and the main thread is created. A new frame is created for the main method and pushed into the thread stack.

![](https://thepracticaldev.s3.amazonaws.com/i/37gign0o3qajy1hrh6t7.png)

```
public static void main(java.lang.String[]);
  Code:
     0: iconst_2
     1: iconst_3
     2: invokestatic  #2                  // Method add:(II)I
     5: istore_1
     6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
     9: iload_1
    10: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
    13: return
```

The main method has two variables: _args_ and _result_. Both reside in the local variable table. The first two bytecode commands of main, _iconst_2_, and _iconst_3_, load the constant values 2 and 3 (respectively) into the operand stack. The next command _invokestatic_ invokes the static method add. Since this method expects two integers as arguments, _invokestatic_ pops two elements from the operand stack and passes them to the new frame created by the JVM for _add_. _main’s_ operand stack is empty at this point.

![](https://thepracticaldev.s3.amazonaws.com/i/5ftifjj2j954410024q2.png)

```
public static int add(int, int);
  Code:
     0: iload_0
     1: iload_1
     2: iadd
     3: ireturn
```

In the _add_ frame, these arguments are stored in the local variable array. The first two bytecode commands, _iload_0_ and _iload_1_ load the 0th and the 1st local variables into the stack. Next, _iadd_ pops the top two elements from the operand stack, sums them up, and pushes the result back into the stack. Finally, _ireturn_ pops the top element and passes it to the calling frame as the return value of the method, and the frame is discarded.

```
public static void main(java.lang.String[]);
  Code:
     0: iconst_2
     1: iconst_3
     2: invokestatic  #2                  // Method add:(II)I
     5: istore_1
     6: getstatic     #3                  // Field java/lang/System.out:Ljava/io/PrintStream;
     9: iload_1
    10: invokevirtual #4                  // Method java/io/PrintStream.println:(I)V
    13: return
```

_main’s_ stack now holds the return value of _add_. _istore_1_ pops it and sets it as the value of the variable at index 1, which is _result_. _getstatic_ pushes the static field _java/lang/System.out_ of type _java/io/PrintStream_ onto the stack. _iload_1_ pushes the variable at index 1, which is the value of result that now equals 5, onto the stack.

So at this point the stack holds 2 values: the ‘out’ field and the value 5. Now _invokevirtual_ is about to invoke the _PrintStream.println_ method. It pops two elements from the stack: the first one is a reference to the object for which the println method is going to be invoked. The second element is an integer argument to be passed to the println method, that expects a single argument. This is where the _main_ method prints the result of _add_. Finally, the _return_ command finishes the method. The main frame is discarded, and the JVM process ends.

## “Write Once, Run Anywhere”

So what makes Java platform-independent? It all lies in the bytecode.

As we saw, any Java program compiles into standard Java bytecode. The JVM then translates it into the specific machine instructions at runtime. We no longer need to make sure our code is machine-compatible. Instead, our application can run on any device equipped with a JVM, and the JVM will do it for us. It’s the job of the JVM’s maintainers to provide different versions of JVMs to support different machines and operating systems.

This architecture enables any Java program to run on any device having a JVM installed on it. And so the magic happens.

## Final Thoughts

Java developers can write great applications without understanding how the JVM works.

However, digging into the JVM architecture, learning its structure, and realizing how it interprets your code will help you become a better developer. It will also help you tackle really complex problem from time to time 🙂

PS. If you’re looking for a deeper dive into the JVM and how all of this relates to Java exceptions, look no further! ([It’s all right here](https://blog.overops.com/the-surprising-truth-of-java-exceptions-what-is-really-going-on-under-the-hood/?utm_source=devto).)

_Written by [Tzofia Shiftan](https://twitter.com/tzofias). First published on [The OverOps Blog](https://blog.overops.com/jvm-architecture-101-get-to-know-your-virtual-machine/?utm_source=devto)._