Memory is a very important system resource.
It is the intermediate bridge between the hard disk and the CPU, and carries the real-time operation of the operating system and applications.
Below is architecture of JVM memory runtime data area :

Let us first focus on Thread Private Area.
What You Need
- About 10 minutes
- A favorite text editor or IDE
- Java 8 or later
1. Program Counter Register
The PC register is used to store the address of the next bytecode instruction.
It is a tiny piece of memory almost negligible.
Each thread has its own program counter, so it has the same life cycle as the thread’s life cycle.
There is only one method executing on a thread at any time, the so-called current method.
If the current thread is executing a Java method, the PC register records the address of the JVM bytecode instruction, and if it is executing a native method, it is an undefined value.
When the bytecode interpreter works, it selects the next bytecode instruction to be executed by changing the value of this program counter.
It is the only area that does not have any OutOfMemoryError.
2. Stacks
When each thread is created, it will create a stack.
A stack is private to the thread and have the same life cycle as the thread.
There are only two operations that the JVM directly operates on stack :
- when method execution starts, it will be pushed into stack;
- when method execution ends, it will be popped out of stack.
2.1 Stack Size
The stack does not have garbage collection, but there is possible error on it.
If the stack size requested by the thread exceeds the maximum size allowed by jvm, it will throw a StackOverflowError :
public class StackErrorTest {
public static void main(String[] args) {
main(args);
}
}
Below is the output of above code snippet :
Exception in thread "main" java.lang.StackOverflowError
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
at StackErrorTest.main(StackErrorTest.java:3)
To change the stack size, we can use the -Xss flag, for instance, java -Xss1024k
We can also use the -XX:ThreadStackSize flag to configure the stack size, for instance, java -XX:ThreadStackSize=1024
2.2 Stack Frame
Each thread has its own stack, in each stack, there are stack frames.
Each stack frame is a method being executed on this thread.
In an active thread, at a point in time, there is only one active stack frame.
The top stack frame is called the current stack frame, and the method corresponding to the current stack frame is the current method.
All bytecode instructions run by the execution engine operate only on the current stack frame.
If other methods are called in this method, the corresponding new stack frame will be created and placed at the top of the stack, which will become the new current stack frame.
The stack frames contained in different threads are not allowed to refer to each other, that is, it is impossible to refer to the stack frame of another thread in one stack frame.
If the current method calls other methods, when the method returns, the current stack frame will return the execution result of this method to the previous stack frame.
Then jvm discards the current stack frame, making the previous stack frame become the current stack frame again.
Java methods have two ways to return functions, one is normal function return, using the return instruction, and the other is throwing an exception.
No matter which way, the stack frame will be popped up.
Each stack frame stores : Local Variables, Operand Stack, Dynamic Linking, Return Address.

2.2.1 Local Variables
Local Variables is used to store method parameters and local variables defined in the method body, including :
- Basic Data Types (boolean, byte, char, short, int, float, long , double)
- Object Reference (it is not equivalent to the object itself)
public class LocalVariablesTest {
public static void main(String[] args) {
String greeting = "hello world";
LocalVariablesTest test = new LocalVariablesTest();
test.sayHi(greeting);
}
private void sayHi(String greeting) {
System.out.println(greeting);
}
}
For above java class, below are the elements in local variable table of its main method :

The local variable resides on the stack, which is private to each thread, so there is no data security issue.
Variables in the local variable table are only valid in the current method call.
Normally the larger the stack is, the more nested methods can be called.
For a method call, the more parameters and local variables it has, the more stack space it will occupy, resulting in fewer nested method calls.
For a non static method, the first element in its local variables table is this.
import java.util.Arrays;
public class LocalVariablesTest2 {
public static void main(String[] args) {
}
private static class Cooker {
private String name;
public Cooker(String name) {
this.name = name;
}
public void cook(String[] dishes) {
int num = dishes.length;
System.out.println(this.name + " cooks " + num + " dishes : "
+ Arrays.toString(dishes));
}
}
}
Below is the local variables for cook method of above Cooker class, the first element is this :

2.2.2 Operand Stack
In addition to the local variable table, each stack frame also contains a operand stack (Last-In-First-Out).
In the process of method execution, the operand stack is mainly used to save the intermediate results and as a temporary storage space for variables in the calculation process.
It is a work area of the JVM execution engine and it can only have two operations: push and pop.
Below is a class Addition which contains two methods : inc and add, method add is called in the method inc.
public class OperandStackTest {
public static void main(String[] args) {
class Addition {
int add(int a, int b) {
return a + b;
}
int inc(int a) {
return add(a, 1);
}
}
int num = 99;
System.out.println(num + " increse by 1 = " + new Addition().inc(num));
}
}
The bytecode instructions for inc method is below :

The bytecode instructions for add method is below :

Let us have a look at how execution engine uses operand stack to process a call of inc method in main method.
The first byte code instruction in inc method is aload_0, it means that the object reference in the local variable at 0 is pushed onto the operand stack :

The second instruction is iload_1, it means that the value of the local variable at 1 is pushed onto the operand stack :

The third instuction is iconst_1, it means to push the int constant 1 onto the operand stack :

The fourth instuction is invokevirtual, it means the argument values and object reference are popped from the operand stack and that new frame is created on the Java Virtual Machine stack for the method being invoked.
The object reference and argument values are sequentially assigned to the local variables of the new frame, with object reference in local variable 0, arg1 in local variable 1 and so on :

Now the current stack frame is add method, its first bytecode instruction is iload_1, it means that the value of the local variable at 1 is pushed onto the operand stack :

The second instruction is iload_2, it means that the value of the local variable at 2 is pushed onto the operand stack :

The third instruction is iadd, it means that the values are popped from the operand stack.
The int result is value1 + value2.
Then the result is pushed onto the operand stack :

The fourth instruction is ireturn, it means that value is popped from the operand stack of the current frame and pushed onto the operand stack of the frame of the invoker.
Any other values on the operand stack of the current method are discarded :

Now we are back to stack frame of inc method, next instruction after invokevirtual is ireturn, so inc method pops the value from the operand stack and pushed onto the operand stack of its invoker main method :

2.2.3 Dynamic Linking
When a java source file is compiled into a bytecode file, all variable and method references are symbolic reference which are stored in the constant pool of the Class file.
The function of dynamic linking is to convert these symbolic references into direct references of the calling method :

2.2.4 Return Address
Return address is a samll block of memory in each stack frame to hold the value of the PC register that called this method.
There are two ways to mark the end of a method :
- Method’s execution is completed normally;
- An unhandled exception occurred, the method is completed abnormally.
No matter which way, after the method exits, it returns to the point where the method was called.
When the method exits normally, the value of the caller’s PC register is used as the return address, it is the address of the instruction following the instruction that called the method.
When exiting by exception, the return address is determined by the exception table.
Below is an example in which there is a main method who calls div method, and if there is any exception about div method, it will be caught in main method :
1public class ReturnAddressTest {
2 public static void main(String[] args) {
3 try {
4 div(1, 0);
5 } catch (Exception e) {
6 e.printStackTrace();
7 }
8 }
9
10 private static int div(int a, int b) {
11 return a / b;
12 }
13}
Below is the byte code instructions of main method :

When execution engine reaches instruction number 2, it will create a new stack frame in order to execute div method.
If div method exits normally, the return address of div method will be instruction number 5, beacause it is the next instruction right after instruction number 2.
If div method exits with exception, according to the exception table of main method, the return address of div method will be instruction number 9 :

From the exception table of main method, we can know that if the area from instruction 0 to 6 has any unhandled exception, it will go to instruction 9 to handle it.
According to the line number table, we can know that the area from instruction 0 to 6 is the area from line 4 to line 5 of this class :

3. Native Method Stack
A native method is a java method with native modifier.
For instance, in the class Thread, we use method start to create a new thread.
If we have a look into start method, it calls a native method start0 :
/**
* Schedules this thread to begin execution. The thread will execute
* independently of the current thread.
*
* <p> A thread can be started at most once. In particular, a thread can not
* be restarted after it has terminated.
*
* @throws IllegalThreadStateException if the thread was already started
*/
public void start() {
synchronized (this) {
// zero status corresponds to state "NEW".
if (holder.threadStatus != 0)
throw new IllegalThreadStateException();
start0();
}
}
private native void start0();
The implementation of a native mthod is written in another programming language such as C/C++.
JVM uses a tool called JNI (Java Native Interface) as a bridge to interact with C/C++ code.
Like for java methods, there is jvm stack, for native methods, there is native method stack to manage the invocations of native methods.
Native method stack is also thread private.