Overview
As a Java Programmer we are sometimes so engrossed in writing the code and getting the feature/story delivered that we often ignore the performance impact of the code written. This generally results in the applications crashing during the stress test or more often in the production environment. The impact of application crashing in the production system can result in financial and reputational damage to the firm.
This rationale is sufficient enough to make sure our application should respond within the agreed SLAs with zero downtime.
In this article we will cover a basic understanding of how memory management works in Java with a simple working example which will help us design/debug Java applications from a performance point of view.
Memory Model
Note: I have intentionally left low level details and further classification of Java memory model and kept it very high level for someone new to get a basic understanding of the topic.
Java Memory is divided into three major components:
- Thread Stack
- Heap
- Metaspace
Thread Stack
- Every thread will have its own stack ie. thread’s cannot access each other’s stack
- Local variables are stored on the stack
- Local variables reference the objects created in heap
- Stores the values for the primitive data types i.e. int, double, float, long etc.
Heap
- Stores Java object
- String pool resides in the heap
Metaspace
- Stores metadata about classes, methods. For e.g. which methods are compiled into bytecode
- Stack for static variables
- Stores primitive static variables
- Stores references to the static object which points to the actual object stored in heap
- Variables in the metaspace are permanent during the lifecycle of the application thus the corresponding object in the heap will never be garbage collected
- All classes and threads within a Java application have access to the metaspace
Let’s understand the memory allocation using the below example.
public class Person { private int age; private int salary; String department; public Person(int age, int salary) { this.age = age; this.salary = salary; } public Person(int age, int salary, String department) { this.age = age; this.salary = salary; this.department = department; } public String getDepartment() { return department; } public void setDepartment(String department) { this.department = department; } public int getSalary() { return salary; } public void setSalary(int salary) { this.salary = salary; } } public class SampleMemoryModel { private static final int FIXED_INCREMENT_IN_PERCENT = 2; public static void main(String[] args) { Person p = new Person(25, 30000, “ACCOUNTING”); int percentage = 5; SampleMemoryModel smm = new SampleMemoryModel(); smm.calcAndAdjustSalary(p, percentage); smm.changeDepartment(p,”IT”); System.out.println(“New salary:” + p.getSalary() + ” Department:” + p.getDepartment()); } public void calcAndAdjustSalary(Person person, int perIncr) { int totalIncrement = perIncr + FIXED_INCREMENT_IN_PERCENT; int newSalary = person.getSalary() + person.getSalary()*totalIncrement/100; person.setSalary(newSalary); } public void changeDepartment(Person person, String dept) { person.setDepartment(dept); } } |
The program starts execution from the main method.
- A person object with age=25 and salary=30000 is created in the heap
- A String object is created in the heap and referenced by department property of above created Person object
- A variable referencing to the Person object is created in the Main thread stack
- Also a static variable(FIXED_INCREMENT_IN_PERCENT) with value 2 is created in Metaspace.
- A primitive data type percentage is created on stack with value 5
- A SampleMemoryModel object is created in heap
- A variable smm is created in stack, referencing to the SampleMemoryModel object in heap
As the calcAndAdjustSalary method is called, the variables created in the main method becomes inaccessible and is shown in grey color in the below diagram
- As Java is pass by value, a person is created on the stack. Since this is copy of variable p, it points to the same Person object created in the main method
- perIncr, totalIncrement, newSalry are created on the stack as these are primitive data types
Post the execution of calcAndAdjustSalary, all the variables created in the stack are removed. The control goes to the main method and then the changeDepartment method executes.
- Stack variables in the main method are not accessible by changeDepartment
- Copy of p ie. person (ie. reference to the Person object created in main method) is created in stack
- A new variable dept is created in stack referencing to String object “IT” created in heap
- The reference of department in person object is changed to String object “IT”
- This leaves the String object “ACCOUNTING” with no reference ie. this object is eligible for garbage collection
After the execution of changeDepartment method the control goes back to main method ie. all the variables created during execution of changeDepartment are removed.
The main method prints the New salary and Department and the program ends, thus destroying all the three ie. stack, heap and metaspace
To summarize the article focused on the below items:
- Why understanding Java Memory Model is important from a developer’s point of view
- The high level overview of each type of memory ie. stack, heap and metaspace
- The simple Java working example to show how the objects are created, referenced and destroyed