Java Memory Management
In this article, we talk about Java memory management and how to write optimal code to use memory allocation efficiently.
1. Java Memory Management: Introduction
Any computer program we write needs basic essential resources like CPU for math & computing, and finally memory for managing the data. Memory is a limited resource and it has to manage effectively.
For memory management in Java, we need to first understand how the memory is allocated, referenced, de-allocated, and finally, what happens to the memory after de-allocation. Once the memory de-allocated, the memory would be marked free for future usage.
In Java, if the memory is allocated to a variable and the variable is still referencing it, the memory would be allocated not available for other variables/program. Hence, if a code is written to allocate a resource for usage with no clean-up after the job done, the allocated memory unit would be locked forever leading to a memory leak, eventually, leading to an error called java.lang.OutOfMemoryError
.
2. Types of Variables
In any program, variables are the place holders of the data units which are used in computing. The following are the types of variables available in the Java program:
- Static variables
- Class variables
- Instance variables
- Method variables
3. Reference Type
Irrespective of the type of variables, the variables have datatype or class denoting the stereotype of values of objects the variables may refer to. If the variable is declared with a basic datatype then the access to the values is of Value Type.
Here in the example, age
is the variable with int datatype and age
is of value type. The variable pinCode
is an instance of Integer class.
int age = 25; Integer pinCode = new Integer(12345);
4. Java Memory Model
Java uses the JMM – Java memory model to create, maintain, and recycle memory spaces. It is worth noting whenever you compile a Java code, the java program is compiled to a platform-independent byte-code. The byte-code is finally interpreted and executed by a Java Virtual Machine, which provides the memory resources to run the program. The following are the memory components of a JVM instance:
- Stack
- Heap Memory
- Perm-gen space or Metaspace ( > Java 8)
- String pools
Let us discuss each of the components with the help of Java Program.
BookReader.java
package com.javacodegeeks.examples; import java.util.Arrays; import java.util.List; /** * Class example for reading a book. * * @author ezmairs */ public class BookReader { public BookReader(String name, int numOfPages, String path) { this.name = name; this.numOfPages = numOfPages; this.path = path; } private static final int READING_SPEED_MINUTES_PER_PAGE = 30; /** * Name of the book. */ private String name; /** * Number of pages. */ private int numOfPages; /** * Path on the disk to the book. */ private String path; /** * Method to read a book. * * @param path Path, on the disk, to book. */ public void readTheBook() { System.out.printf("Reading the book %s.\n", name); System.out.printf("Number of pages in the book %d.\n", numOfPages); int timeInMinutesForReading = READING_SPEED_MINUTES_PER_PAGE * numOfPages; List lines = readAllLines(path); for(String line: lines) { System.out.println(line); } System.out.printf("Time taken to read the entire book is %d min(s).\n", timeInMinutesForReading); } /** * Read all lines from the book. * * @param path Path, on the disk, to book. * @return */ private List readAllLines(String path) { List lines = Arrays.asList(new String[] { "Hello this is sample line 1", "Hello this is sample line 2", "Hello this is sample line 3" }); return lines; } public static void main(String[] args) { // Reader BookReader bookReader = new BookReader("Harry Potter and the Sorcerer’s Stone", 288, "harryPotter.pdf"); // Read the book bookReader.readTheBook(); } }
4.1 Stack
The stack is a Last-In-First-Out data structure to store information about method calls, and variables, values, and references, in each method call. For example, in the method call above java class, the public method readTheBook(String path)
calls the private method readAllLines(String path)
. In this case readTheBook(String path)
method parameters, the variable line will be pushed into the stack first and then those of the method readAllLines(String path)
.
In Java, each thread has a stack and hence they are also called Thread stacks. Each thread stack has its own set of variables visible only to that thread. No two threads can access variables from each other. Instead, they have their own copy of values for the local variables in the method.
4.2 Heap
Heap is a tree-like data structure to store the objects. The moment the processor executes assignment with a new keyword, for example, BookReader bookReader = new BookReader("Harry Potter and the Sorcerer’s Stone", 288, "harryPotter.pdf");
, it creates an object on the heap and assigns the reference to the object to the variable.
4.3 Permgen space or Metaspace ( > Java 8)
Perm-gen space or Metaspace is the code segment of the whole program where compiled classes are prepared, linked, and loaded onto the memory. Check out another article on Permgen space. Since Java version 8 and above Permgen space is renamed to Metaspace and there is no limit on the amount of memory allocated to the Metaspace, unlike Permgen space where we had to tune the JVM to allocate specified memory sizes before the start of the program.
In the example above, the class BookReader
and the static variables such as READING_SPEED_MINUTES_PER_PAGE
are hosted in the Permgen-space/Metaspace.
4.4 String Pools
String pools are special portions in the heap to store repetitive String values used by the program. In the example below, lastName
and surname
holds the same value “Doe”, and hence only one instance of String is created in the String pool and is referenced by two variables. However, the value held by name
takes a different string value and a different String instance in the pool. In this way, how much ever times you create the reference to the same common String value, extra memory is not created. There-by saving a lot of memory space.
String name = "John";
String lastName = "Doe";
String surname = "Doe";
In the example above, the values (variable lines
) in the return statement of the method readAllLines
are stored in the String pool.
5. Garbage Collector
We just discussed various components of the Java Memory Model. We know Stack, Permgen/Metaspace, and String pools are controlled and effectively recycled.
However, in the case of objects created on the heap, the programmer can create any number of objects. Unlike C/C++, which the memory is deallocated by the programmer manually in the code, there is no way a programmer can release the objects.
Hence, every JVM’s have something known as Garbage Collector, which is an independent thread that is responsible for creating, maintaining, and freeing up the memory when it is not required. This is a background daemon that looks for unused memory, cleans up the memory, and mark it for reuse. Here unused memory means the objects which are not referenced by any variables and hence not reachable.
A typical garbage collector has two steps:
- Marking – this step garbage collector scans all objects to mark used and unused objects. This is time-consuming as it has to check the reachability of each of the objects. In the above diagram, blue-colored boxes show used objects and are not eligible for cleanup. The yellow boxes show unreachable objects and are marked for cleanup.
- Deletion
- Deletion: this step will delete all the unused objects and mark for reuse for memory allocator to use those deleted spaces. In the diagram, Step-2 shows white boxes showing deleted objects and are free to be allocated by the memory allocator. However, as shown in the figure there are no contagious memory locations that make memory allocation inefficient.
- Deletion with compaction: this step is an improvement over the above step wherein deletes the objects and copies. In the diagram, Step-2 (improved) shows white boxes showing deleted objects and are free to be allocated by the memory allocator. As shown in the figure the white spaces are moved to the end to form contiguous memory locations.
For more information on garbage collection check the official Java documentation or our article here.
6. Generations in Memory
To avoid memory fragmentation and better manage the scanning of objects the memory is divided into spaces. Below are the spaces in the memory:
- Eden Space (heap): This space is the first and foremost where the objects are created. The memory allocator looks for memory in this space. If the Eden space is out of memory, minor GC is triggered and this scans for reachable and non-reachable objects. Non-reachable objects shall be freed.
- S0/S1 Survivor Space (heap): This space is for the survivors of the minor GC above. This along with Eden space forms the younger generation.
- Old Generation Space (heap): All objects in the younger generation which crossed the max-age threshold and likely to stay long are put in Old Generation space.
- Permanent Generation Space (non-heap): this space is for class variables, classes, and constants.
7. Types of Garbage Collectors
Before getting into types of The following are the types of collectors based on the algorithm it uses to mark and sweep (deletion):
7.1 Serial Garbage Collection
This is the simplest of garbage collection. This is single-threaded and freezes the other threads while executing garbage collection. The serial GC executes mark-deletion for the young generation and mark-deletion-copy for the old generation. This is only suitable for small programs. The below options are used to choose serial GC.
java -XX:+UseSerialGC BookReader.class
7.2 Parallel Garbage Collection
Parallel Garbage Collection is suitable to exploit multiple cores on the computer. It still executes mark-deletion for the young generation and mark-deletion-copy for the old generation. The parallel GC uses multiple concurrent threads for mark-deletion-compact cycles.
You can configure the number of concurrent threads using the XX:ParallelGCThreads=N
option. The below options are used to choose parallel GC.
java -XX:+UseSerialGC XX:ParallelGCThreads=N BookReader.class
For more information check the official Java documentation.
7.3 CMS Garbage Collectors
CMS garbage collection is an improved and advanced version. This algorithm scans heap memory using many threads. Any application which needs minimal pauses would take advantage of the Concurrent-Mark-Sweep algorithm.
It uses the parallel stop-the-world mark-copy algorithm in the Young Generation and the mostly concurrent mark-sweep algorithm in the Old Generation.
It executes parallel mark-deletion for the young generation and concurrent mark-deletion-copy for the old generation.
The below options are used to choose the CMS GC.
java -XX:+UseConcMarkSweepGC BookReader.class
For more information check the official Java documentation.
7.4 G1 Garbage Collectors
The garbage-first garbage collector is a unique garbage collector that divides memory into up to 2048 regions making them into sets of Eden, survivor, and old generation logical sets called Collection sets. The G1 GC avoids memory fragmentation by live copying the objects from one more collection set.
The below options are used to choose the G1 GC.
java –XX:+UseG1GC BookReader.class
For more information check the official Java documentation.
8. Writing Memory Efficient Code
The following are the guidelines to write memory efficient code which uses the memory optimally:
- Plan your data structure usage. For example, the below code creates an object and adds only one element. In this case, it internally reserves an array of 10 elements, and if you are to use only one element, the rest of the spaces of no use.
List names = new ArrayList(); names.add("John");
- Be very careful when you use
java.lang.String
concatenation for building a large volume of String. Usejava.lang.StringBuffer
.
// Wrong usage String message = ""; for(int i = 0; i < 1000; i ++) message = message + " " + Integer.valueOf(1000).toString(); System.out.println("Big String : " + message); // Right usage StringBuffer messageBfr = new StringBuffer(); for(int i = 0; i < 1000; i ++) messageBfr.append(" " + Integer.valueOf(1000).toString()); System.out.println("Big String : " + messageBfr.toString());
- Avoid using wrappers for the basic datatypes when not needed. For example, in the code below, just to do the simple math of averaging, you do not need to use java.
double average = 0.0; Integer[] numbers = new Integer[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}; Integer sum = 0; for (Integer number : numbers) sum += number; average = sum/numbers.length;
- Use try-with-resources to open resources and close it effectively.
- Responsibly de-reference any created objects by assigning variables to null.
- Use jconsole provided by Oracle to monitor the memory consumption of your programs and effective tune JVM with appropriate GC algorithms.
- Use WeakReference instead of Strong reference wherever possible to avoid the GC unnecessarily holding on to unwanted objects.
9. Executing the sample code
In this section we will execute the programs and see how it is working.
9.1 Prerequisites:
- Java 1.8 installed in the system. Environment variables
JAVA_HOME
set to the Java location andPATH
set to the directory containing javac and java binaries (%JAVA_HOME%/bin
on windows or$JAVA_HOME/bin
on Linux machines) - Source code zip and downloaded to a location (say,
C:\JavaCodeGeeks
. This would be different for Linux) - Eclipse IDE (Photon Release (4.8.0) is used for this example)
9.2 Execution using eclipse
Step 1: Open the Eclipse IDE.
Step 2: Click On File >> Import.
Step 3: From the “Import” menu select “Existing Projects into Workspace”.
Step 4: Click Next.
Step 5: On the next page, click browse and select the root of the example folder (say,C:\JavaCodeGeeks
). Click on the “Finish” button.
Step 6: Ensure Package Explorer is loaded and lists all the files as shown in the figure below.
Step 7: Click on src >> com.javacodegeeks.examples >> JavaMemoryManagement
Step 8: Right-click on BookReader.java
, from the menu, choose
“Run As” >> “Java Application”
See the sample output as below:
Reading the book Harry Potter and the Sorcerer’s Stone. Number of pages in the book 288. Hello this is sample line 1 Hello this is sample line 2 Hello this is sample line 3 Time taken to read the entire book is 8640 min(s).
10. Download the Eclipse Project
That was an article about memory management in Java.
You can download the full source code of this example here: Java Memory Management