Not So Common Memory Leaks & How to fix them?

Excess memory usage causes applications to slow down and become unresponsive. It can degrade the performance of other applications, and may cause a program to crash with an Out of Memory error. Very often, the root cause of this is a memory leak.

Most memory leaks are fairly easy to trace using a heap dump analysis tool. They’re likely to manifest as either a single object that keeps growing over time, or a proliferation of the same type of object. You can find out more about the most common memory leaks in this article: Common Memory Leaks in Java and How to Fix Them.

Sometimes it’s not so easy. Memory leaks can be caused by obscure and poorly-understood issues, and this type of error can be very hard to debug. In this article, we’ll look at a few of these, and discuss how to diagnose and fix the problem.

Video

In this video, our Architect Ram Lakshmanan has explained about the not so common memory leaks that we face in our Java applications and how to diagnose and resolve them quickly. 

Less Common Causes of Memory Leaks

We’ll be looking at the following issues that can cause memory leaks:

  1. Slow finalizer() methods;
  2. Uncleared ThreadLocal() variables;
  3. Mutated keys in collections.

These errors are less obvious when using a heap dump to detect memory leaks. The good news is that the HeapHero tool’s machine learning algorithms can often identify the problem immediately, and recommend ways to solve the issue.

Let’s look at each of these issues in turn.

1. Slow finalizer() Methods

The finalizer() method is an empty placeholder defined in java.lang.Object. Since all classes are descended from Object, any Java class may optionally override this method. In the early days of Java, developers were encouraged to use this method to perform any clean-up or final tasks that should be carried out just before the object is garbage collected.

However, it was found to be unreliable, since there are no guarantees as to how soon the method will be carried out, or if, in fact, it will ever be run at all. It was therefore deprecated as of Java 9. Moreover, the finalizer() can, in some circumstances, cause memory leaks, as we’ll see.

To understand why this is the case, let’s look at how the garbage collector (GC) deals with the finalize() method.

  • GC searches for objects that no longer have valid references pointing to them, and marks them as finalizable.
  • In the next stage, if the finalizable object overrides the finalize() method, it is added to the finalization queue. If it does not, it is immediately marked as eligible for garbage collection.
  • The finalization queue is a single-threaded process that executes the finalize() method of each object in the queue sequentially. Once the finalize() method has been run, the object is marked as eligible for GC.
  • If any objects remain in the queue when the program terminates, their finalize() methods will never be run.

So how can this cause a memory leak?  Sometimes, the finalizer() method doesn’t complete in a timely manner. This may be because it’s waiting for a slow resource, such as a storage device. A dropped network connection may cause it to wait indefinitely for a response. Or it may simply have a bug in it. While it’s waiting, the entire queue of finalizable objects is held up, since the process is single-threaded.

This means no objects can be garbage collected until this method is completed, and memory usage begins to build up, possibly resulting in an Out of Memory error.

To fix or prevent this happening, we need to remove the finalize() method from the offending object, and deal with finalization tasks in another way. Options include using try-with-resources blocks, or including a close() method in the object’s class, and ensuring all calling applications use it.

For a sample program illustrating this issue, a fuller coverage of the topic, and step-by-step debugging instructions, read Memory Leak Due to Time-Taking finalize() Methods.

2. Uncleared ThreadLocal Variables

Instance variables are visible throughout the class; local variables are visible only within the current block. Variables defined as ThreadLocal are different yet again: they are visible anywhere within the current thread, but not outside the thread. An example of where they are useful is in an application that creates a thread to process each online transaction. For example, the transaction ID may be used in various places within the thread, but it’s meaningless outside of it.

These variables are kept alive until either the thread completes, or they are removed using the class’s remove()  method.

If for any reason threads don’t terminate when they should, this can lead to a buildup of ThreadLocal() variables in memory.

To fix the problem, ensure all threads terminate correctly, and use the remove() method to get rid of ThreadLocal() variables that are no longer needed.

For a sample program illustrating this issue, a fuller coverage of the topic, and step-by-step debugging instructions, read Memory Leak Due to Uncleared ThreadLocal.

3. Mutated Keys in Collections

Java Collections are extremely useful for storing structured data in memory. Examples include Lists, Maps and Sets. For example, the HashMap is useful for storing and quickly retrieving key-value pairs. It’s optimized by storing data in buckets, and using a hash code to determine which bucket it will use to place the data.

Theoretically, it should be impossible to create duplicate entries, since the put() method simply replaces the old value with the new if it detects that the key already exists.

This works really well – until a badly-coded application accidentally changes the value of the key entry. This makes the entry unreachable via the hash code, since the hash code and the bucket no longer match. Duplicates won’t be detected, and the remove() method will silently fail. This can result in a HashMap that grows continually, and in time, it can cause degraded performance or an Out of Memory crash.

To prevent this from happening, always use the final modifier when defining variables that will be used as keys in a map. In this way, the keys become immutable, and the issue will not occur.

For a sample program illustrating this issue, a fuller coverage of the topic, and step-by-step debugging instructions, read Memory Leak Due toMutable Keys in Collections.

Conclusion

In this article, we’ve taken a look at some of the lesser-known and unusual causes of memory leaks.

These issues can be really difficult to debug. Fortunately, the HeapHero tool can often detect these and other memory problems instantly from analyzing a heap dump, and recommend solutions. This can save you hours of frustration, and get your systems back on the road with very little time lost.

Share your Thoughts!

Up ↑

Index

Discover more from HeapHero – Java & Android Heap Dump Analyzer

Subscribe now to keep reading and get access to the full archive.

Continue reading