Home Post Architecture

Java Memory Leaks & Heap Dumps (Capturing & Analysis)

Mar 31, 2024

In Java, memory leaks can occur when objects that are no longer needed continue to be referenced, preventing the garbage collector from reclaiming the memory they occupy.

The garbage collector is responsible for automatically managing memory in Java by identifying and freeing up memory occupied by objects that are no longer reachable or referenced by any active part of the program.

When an object is no longer reachable, meaning there are no more references to it from any active part of the program, it becomes eligible for garbage collection. The garbage collector can then safely reclaim the memory occupied by that object. However, if there is still at least one reference to the object, even if it is no longer needed, the garbage collector cannot collect it, and a memory leak occurs.

While the garbage collector in Java is a powerful tool for managing memory, it is not infallible, and memory leaks can still occur even in well-written applications.

What Is a Memory Leak ?

A memory leak refers to a situation where objects that are no longer needed or used by a program remain in memory and are not properly deallocated. These objects consume memory resources unnecessarily, leading to inefficient memory usage and potential performance issues.

In languages like Java, where automatic memory management is handled by the garbage collector, memory leaks occur when objects are still referenced and held in memory, preventing the garbage collector from reclaiming the memory they occupy. As a result, even though the objects are no longer used, they persist in memory, leading to a gradual accumulation of unreleased memory over time.

If memory leaks are not addressed, they can progressively consume more and more system resources, leading to a point where the application exhausts the available memory. This can result in a critical error known as java.lang.OutOfMemoryError, which causes the application to terminate abruptly.

Symptoms

If you observe the heap size consistently growing over several days, with a significant portion of the retained objects occupying the Tenured Generation (Tenured Gen) space, it suggests the accumulation of long-lived objects that survive multiple garbage collection cycles.

This can be an indication of potential memory leaks or inefficient memory management.

Diagnosis

Analyzing the code is an important step in identifying potential memory leaks and determining which objects are accumulating in the heap.

While it can be challenging to pinpoint the exact objects without profiling or monitoring tools, examining the code for potential candidates is a good starting point.

Moreover, Profiling tools or heap analysis can provide more precise insights into object retention and help identify the specific objects causing the memory buildup.

Heap Dump

Analyzing a heap dump is an effective way to identify and analyze memory leaks in Java applications.

A heap dump is commonly stored in a binary file with the .hprof extension. This file contains a snapshot of the entire Java heap memory at the time the dump was taken. The size of the heap dump file is typically proportional to the size of the heap at the moment the dump was captured.

Ways to capture Java heap dumps

There are several ways to capture Java heap dumps, depending on your requirements and the tools available.

1) Spring Boot Actuator

In a Spring Boot application, one convenient way to obtain a heap dump is by utilizing the heapdump Actuator endpoint.

To enable the heapdump Actuator endpoint, we need to configure the Actuator module and ensure that the heapdump endpoint is exposed.

1.1) Ensure that the project has the Actuator dependency included in pom.xml.

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-actuator</artifactId>
</dependency>

1.2) Make sure that Actuator endpoints are enabled. This can be done in the application.properties file.

management.endpoints.web.exposure.include=heapdump

To obtain a heap dump using the Actuator endpoint, you can perform a curl command or open the URL https:///actuator/heapdump in a web browser. This will trigger the generation of a heap dump file, which can then be downloaded.

2) JDK Tool

The JDK (Java Development Kit) provides multiple tools that offer different ways to capture heap dumps, catering to various scenarios and preferences.

2.1) 'jmap' tool

The jmap tool is primarily used for printing statistics about memory in a running JVM, but it can also be used to capture heap dumps. Here's an example command to capture a heap dump using jmap:

jmap -dump:live,format=b,file=/Users/nkchauhan/heapdump/heapdump_9379.hprof 9379

Here's a breakdown of the command options:

-dump: This option specifies that a heap dump should be captured.

live (optional): The live option indicates that only live objects should be included in the heap dump. If omitted, all objects, including unreachable objects, will be included.

format=b: This option specifies the format of the heap dump file. The b format represents the binary format.

file=: This option defines the file path and name for the heap dump file. Specify the desired location where the heap dump file should be saved.

<pid>: This is the Process ID (PID) of the Java process for which you want to capture the heap dump. Replace with the actual process ID of the target JVM.

2.2) 'jcmd' tool

Since jcmd works by sending command requests to the JVM, it requires direct access to the JVM process. Therefore, you should run jcmd on the same machine where the Java process you want to capture the heap dump for is running.

An example command to capture a heap dump using jcmd would look like this:

jcmd 9379 GC.heap_dump /Users/nkchauhan/heapdump/heapdump-1_9379.hprof

Here's a breakdown of the command options:

<pid>: This is the Process ID (PID) of the Java process for which you want to capture the heap dump.

GC.heap_dump: This is the command provided by jcmd to request a heap dump.

<file-path>: This option specifies the path and name of the heap dump file.

3) HeapDump on OutOfMemoryError

The JVM provides command-line options to capture heap dumps. These options can be specified when starting the Java application. Two commonly used options are:

-XX:+HeapDumpOnOutOfMemoryError 

This option instructs the JVM to automatically generate a heap dump when an OutOfMemoryError occurs.

-XX:HeapDumpPath=

This option specifies the directory where the heap dump file should be saved.

Example: java -XX:+HeapDumpOnOutOfMemoryError -XX:HeapDumpPath=/path/to/dump/files MyApp

These options allow you to capture heap dumps based on specific conditions or events, such as out-of-memory errors.

4) Visual VM

VisualVM is a powerful tool with a graphical user interface that provides extensive capabilities for monitoring, troubleshooting, and profiling Java applications. VisualVM offers a convenient way to capture heap dumps through its user-friendly interface.

5) JMX

JMX (Java Management Extensions) is a technology that allows you to monitor and manage Java applications, including the ability to capture heap dumps.

With JMX, you can interact with Java applications using management tools and clients that support the JMX protocol.

5.1) Connecting JConsole to a local process

Starting from Java 6 and above, JMX support is enabled by default in the JVM, and the need for explicitly setting the JMX parameters is not necessary for local connections.

The JVM assigns a default port for JMX connections and exposes it only locally, meaning it can be accessed from the same machine where the application is running.

Start JConsole by executing the jconsole command from your terminal or by running the jconsole.exe file on Windows.

JConsole will display a list of Java processes available for connection. Locate and select the Java process representing your application in the list.

Click the "Connect" button to establish the JMX connection between JConsole and your Java application. JConsole will connect to the Java application using the default JMX settings.

Once the connection is established, you can use JConsole to monitor and manage the Java application. You can view memory usage, thread information, perform diagnostics, capture heap dumps, and more.

5.2) Connecting JConsole to a remote process

To connect JConsole to a remote Java process, you need to configure the remote Java application to allow JMX connections and specify the necessary JMX connection parameters when launching JConsole.

To enable JMX remote access in the remote Java application, you need to set specific system properties.

java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.rmi.port=1098 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=ec2-X-X-X-X.ap-south-1.compute.amazonaws.com -jar my-app-4.0.0.jar &

On your local machine where JConsole is installed, launch JConsole by executing the jconsole command from your terminal or by running the jconsole.exe file on Windows.

In the JConsole user interface, click on the "Remote Process" button or select "Remote" from the "Connection" menu.

Once the connection is established, JConsole will display the remote Java application's management interface, allowing you to monitor and manage the application using JConsole's features.

5.3) Connecting VisualVM to a remote process

Similar to connecting JConsole, you need to enable JMX remote access in the remote Java application.

To connect VisualVM to a remote Java process, you need to configure the remote Java application to allow JMX connections and set up a JMX connection in VisualVM.

java -Dcom.sun.management.jmxremote -Dcom.sun.management.jmxremote.port=1099 -Dcom.sun.management.jmxremote.rmi.port=1098 -Dcom.sun.management.jmxremote.ssl=false -Dcom.sun.management.jmxremote.authenticate=false -Djava.rmi.server.hostname=ec2-X-X-X-X.ap-south-1.compute.amazonaws.com -jar my-app-4.0.0.jar &

Start VisualVM on your local machine and configure a new remote JMX connection.

Once the connection is established, VisualVM will display the remote Java application in the Applications section.

Tools to open and analyze Java heap dumps

There are several tools available for analyzing Java heap dumps, each with its own features and capabilities. Here are some popular tools used for heap dump analysis:

1) Visual VM

Once the heap dump is loaded, VisualVM will provide various features and views to analyze the heap dump.

You can select specific objects in the heap dump views to inspect their details, including fields, references, and retained memory.

2) Eclipse Memory Analyzer (MAT)

Visit the Eclipse MAT website and download the appropriate version of MAT for your operating system. Follow the installation instructions provided.

In the main menu, go to "File" > "Open Heap Dump." Browse and select the Java heap dump file you want to analyze, and click "Open."

Once the heap dump is loaded, MAT will perform an initial analysis of the dump and present you with a summary.

Navigate through the different features provided by MAT to explore memory usage patterns, identify memory leaks, and find optimization opportunities.

MAT allows you to generate reports summarizing the analysis results.

Heap analysis

In the context of heap analysis, various terms are used to describe the size and relationships of objects within the heap. Three important terms often encountered are shallow size, retained size, and deep size.

The shallow size helps estimate the memory usage of individual objects, while the retained size helps identify memory leaks or unnecessary object retention. The deep size provides a holistic view of the memory consumed by an object and all its referenced objects.

Shallow size

The shallow size of an object refers to the memory consumed directly by that object alone, without considering the memory consumed by any objects it references.

It represents the memory required to store the object's fields and metadata. The shallow size does not include the memory occupied by any objects referenced by the fields of the object.

Retained size

The retained size of an object represents the amount of memory that would be freed if the object were to be garbage collected. It includes the shallow size of the object itself, plus the memory consumed by objects that are reachable only through this object.

In other words, it accounts for the memory retained by the object's references, both directly and indirectly. The retained size helps identify the actual impact of an object on memory usage.

Deep size

The deep size of an object represents the total memory consumed by that object, including its own shallow size and the memory consumed by all objects it references, recursively.

It represents the complete memory footprint of the object and all the objects it transitively refers to. The deep size provides a comprehensive view of the memory usage associated with the object and its entire object graph.

avatar

NK Chauhan

NK Chauhan is a Principal Software Engineer with one of the biggest E Commerce company in the World.

Chauhan has around 12 Yrs of experience with a focus on JVM based technologies and Big Data.

His hobbies include playing Cricket, Video Games and hanging with friends.

Categories
Spring Framework
Microservices
BigData
Core Java
Java Concurrency