Core Java

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:

  1. Static variables
  2. Class variables
  3. Instance variables
  4. 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:

Java Memory Management
Java Memory Model
  • 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.

Java Memory Management - Steps in garbage collection
Steps in garbage collection

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:

Java Memory Management - Generations in Memory
Generations in 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. Use java.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 and PATH 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”.

Import Java Memory Management Project
Import Java Memory Management Project

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.

Choose JavaMemoryManagement project
Choose JavaMemoryManagement project

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”

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.

Download
You can download the full source code of this example here: Java Memory Management

Shivakumar Ramannavar

Shivakumar has 16+ years of experience in Java Development, Cloud and also has a lot of passion in Cloud-native Applications and Kubernetes. Shiva has a Bachelor's Degree from Visweswaraiah Technological University, India. Shiva has been involved in the development of IT systems using Java/J2EE, Kubernetes, and has designed tonnes of applications both on-premise and on-cloud. Shiva strongly believes there is a great revolution of cloud-native technologies and that we can create a world of difference through learning, sharing, and caring among the community.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button