10 popular memory leaks in Java

Pravinkumar Singh
Javarevisited
Published in
7 min readJun 19, 2023

--

Grrr memory leaks! You see, in my journey as a software developer, I’ve always been aware that these little things aren’t a big cause for concern… until the moment the Java project underperforms in production. That’s exactly when the panic sets in, and I frantically search for a quick fix to remedy this poor performance.

I recall, in one such instance, the fastest solution was to scale the infrastructure vertically — sounds expensive, right? 🤔. Let’s explore another solution for finding and fixing all such memory leaks during the coding phase itself.

1. Static Fields and Collections

Static fields and collections cause a challenge for garbage collection, as their lifecycle matches the application’s lifecycle. They often lead to memory leaks if not managed carefully.

Example: Static HashMap

Consider the following code snippet, where an User object is placed into a static HashMap and never removed.

public class User {
private String userName;

// Static HashMap storing User objects
private static Map<String, User> users = new HashMap<>();

// Constructor
public User(String userName) {
this.userName = userName;
users.put(userName, this);
}

// other methods
}

The User objects placed into the static HashMap users will never be garbage collected unless explicitly removed from the HashMap.

Solution

To prevent such memory leaks, make sure that objects are removed from static fields or collections when they are no longer in use. One solution is to use a WeakHashMap, which automatically removes entries when the keys are no longer needed.

private static Map<String, User> users = new WeakHashMap<>();

2. Unclosed Resources

Not closing resources, such as streams or connections, can also lead to memory leaks. In this case, the leak occurs outside the Java heap, in native memory or off-heap.

Example: FileInputStream

Consider the following code snippet, which reads data from a file:

public void readDataFromFile(String filePath) {
try {
FileInputStream fis = new FileInputStream(filePath);

// Reading data from the file
} catch (IOException e) {
e.printStackTrace();
}
}

In this example, the FileInputStream is not closed after usage, leading to memory leaks.

Solution

Always close the resources after they are no longer required. Java 7 introduced try-with-resources, which automatically closes the resources when the block ends.

public void readDataFromFile(String filePath) {
try (FileInputStream fis = new FileInputStream(filePath)) {

// Reading data from the file

} catch (IOException e) {
e.printStackTrace();
}
}

3. ThreadLocal Variables

ThreadLocal variables allow multiple threads to have their own instance of a shared object, preventing interference among them. However, misuse of ThreadLocal variables can lead to memory leaks, as objects may persist long after the thread has completed execution.

Example: Custom ThreadLocal

public class CustomThreadLocal {
public static final ThreadLocal<SimpleDateFormat> dateFormatter =
ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));

public String formatDate(Date date) {
return dateFormatter.get().format(date);
}
}

In this example, we store a SimpleDateFormat object in a ThreadLocal variable. However, if the thread is not managed, the SimpleDateFormat object may never be garbage collected.

Solution

To avoid memory leaks in this case, clean up the variables once the thread has completed its task. The following code snippet demonstrates the use of a remove method to accomplish this:

public void cleanup() {
dateFormatter.remove();
}

4. Caching without Limits

Cache stores previously calculated values for faster retrieval. However, an unbounded or improperly managed cache may cause memory leaks.

Example: HashMap-based Cache

public class SimpleCache {
private final Map<String, BigDecimal> cache = new HashMap<>();

public BigDecimal getValue(String key) {
BigDecimal value = cache.get(key);
if (value == null) {
value = calculateValue(key);
cache.put(key, value);
}
return value;
}

private BigDecimal calculateValue(String key) {
// A time-consuming operation to calculate the value
return new BigDecimal("123.45");
}
}

In this example, we use a simple HashMap-based cache to store results. However, this cache is unbounded, leading to memory leaks when there are too many entries.

Solution:

Limit the size of the cache and use a suitable eviction policy. Libraries like Google Guava provide configurable caching solutions. Here’s an example using Guava’s CacheBuilder:

import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;

public class LimitedCache {
private final Cache<String, BigDecimal> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.build();

public BigDecimal getValue(String key) {
BigDecimal value = cache.getIfPresent(key);
if (value == null) {
value = calculateValue(key);
cache.put(key, value);
}
return value;
}

private BigDecimal calculateValue(String key) {
// A time-consuming operation to calculate the value
return new BigDecimal("123.45");
}
}

In this solution, the cache size is limited to 1,000 entries, and the eviction policy is set to remove the oldest entries when the cache reaches its maximum size.

5. Improper use of Event Listeners

Adding listeners to different events is a popular pattern in Java programming. However, not removing listeners when they are no longer needed can lead to memory leaks.

Example: Anonymous Event Listener

class MyButton {
private List<ActionListener> listeners = new ArrayList<>();

public void addActionListener(ActionListener listener) {
listeners.add(listener);
}

public void doAction() {
for (ActionListener listener : listeners) {
listener.actionPerformed(new ActionEvent(this, ActionEvent.ACTION_PERFORMED, "Click"));
}
}
}

public class Main {
public static void main(String[] args) {
MyButton button = new MyButton();

for (int i = 0; i < 10; i++) {
// Adding new anonymous listeners
button.addActionListener(e -> System.out.println("Button clicked"));
}
}
}

Here, we create and add anonymous action listeners to a custom button. However, the listeners are never removed, causing memory leaks.

Solution

To prevent memory leaks caused by event listeners, ensure that listeners are removed when they are no longer required. One solution is to use a WeakReference to hold the event listener. Another approach is to ensure that any long-lived or large objects holding the listeners are removed as soon as they are finished with it:

button.removeActionListener(listener);

6. Uncollected Garbage Collection Roots

Garbage Collection (GC) roots are objects that are always reachable and, therefore, are never garbage collected. Some frequent GC roots include static variables, threads, and local variables in the main thread.

If GC roots hold references to unneeded objects, they prevent these objects from being garbage collected.

Example: Object Not Garbage Collected

public static List<BigDecimal> numbers = new ArrayList<>();

public void getData() {
while (dataAvailable()) {
BigDecimal number = getNextNumber();
numbers.add(number);
}

processData(numbers);
}

In this example, the static numbers variable holds references to objects. As long as the numbers list is not cleared, it will cause a memory leak in the application.

Solution

Ensure that such GC roots release objects when they are no longer needed. In this example, we can clear the numbers list when the processing is finished:

public void getData() {
while (dataAvailable()) {
BigDecimal number = getNextNumber();
numbers.add(number);
}

processData(numbers);
numbers.clear(); // Free up memory
}

7. Mismanaged Thread Pools

Improper management of thread pools can lead to memory leaks, mainly when Java applications use thread pools with an unbounded number of threads or do not release resources.

Example: Unclosed Executors

public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);

for (int i = 0; i < 100; i++) {
executorService.submit(() -> {
// Some actions
});
}
}
}

ExecutorService. However, we do not shut down ExecutorService properly, leading to memory leaks.

Solution

To fix memory leaks related to thread pools, ensure that resources are released and threads are terminated in a controlled manner. Shut down the ExecutorService properly once all tasks have been executed:

public class Main {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);

for (int i = 0; i < 100; i++) {
executorService.submit(() -> {
// Some actions
});
}

// Shutdown the ExecutorService
executorService.shutdown();
}
}

8. Singleton Pattern Misuse

Singleton objects are designed to have only one instance during the lifetime of an application. However, improper usage of the Singleton pattern can lead to memory leaks.

Example: Singleton Object

public class Singleton {
private static final Singleton instance = new Singleton();

private List<BigDecimal> data = new ArrayList<>();

private Singleton() {}

public static Singleton getInstance() {
return instance;
}

public void addData(BigDecimal value) {
data.add(value);
}

// Other methods
}

In this example, the Singleton object holds references to BigDecimal objects through its data list. The data list can grow indefinitely, leading to memory leaks.

Solution

To avoid memory leaks related to Singleton objects, be cautious when using the pattern and ensure that you release or limit the resources consumed by the Singleton instance:

public class Singleton {
private static final Singleton instance = new Singleton();

private List<BigDecimal> data = new ArrayList<>();

private Singleton() {}

public static Singleton getInstance() {
return instance;
}

public void addData(BigDecimal value) {
data.add(value);
}

public void clearData() {
data.clear();
}

// Other methods
}

In the improved solution, a clearData method is added to release the resources held by the Singleton instance.

9. Deep and Complex Object Graphs

Applications with complicated object graphs can become hard to manage, making it difficult to determine when objects can be garbage collected. Memory leaks may arise when unreachable objects remain attached to the object graph.

Example: Customer and Orders

public class Customer {
private List<Order> orders = new ArrayList<>();

public void addOrder(Order order) {
orders.add(order);
}

// Getter and setters
}
public class Order {
private List<Item> items = new ArrayList<>();

public void addItem(Item item) {
items.add(item);
}

// Getter and setters
}
public class Item {
private String name;
private BigDecimal price;

// Getter and setters
}

In this example, Customer objects have references to Order objects, and Order objects have references to Item objects. If a Customer object is no longer needed but is not properly detached from the object graph, memory leaks can occur.

Solution

Ensure that you properly manage object references and relationships. Use techniques like the Observer pattern, or weak references, to avoid such memory leaks:

import java.lang.ref.WeakReference;

public class Customer {
private List<WeakReference<Order>> orders = new ArrayList<>();

public void addOrder(Order order) {
orders.add(new WeakReference<>(order));
}

// Getter and setters
}

10. Third-Party Libraries

Memory leaks can also be introduced by third-party libraries if they have bugs or are misconfigured.

Example: XML Parsing

Some XML parsers, like Xerces, may cause memory leaks when custom EntityResolvers are used.

Solution

To avoid memory leaks due to third-party libraries:

  1. Keep libraries updated to the latest stable version.
  2. Understand how the library works and any potential memory issues.
  3. Configure the library according to best practices and recommendations.

In the case of the XML parser, customizing the EntityResolver or switching to a different XML parser implementation can help avoid memory leaks.

// Customize EntityResolver
public class CustomEntityResolver implements EntityResolver {
// Implementation to resolve entities
}

I hope this was informative and productive for your next JIRA dev task.

Hope you like it

Peace!

--

--