This content originally appeared on DEV Community and was authored by JillThornhill
Your Java application has memory issues: maybe it’s throwing OutOfMemoryErrors, or maybe you’re experiencing degraded performance or frequent timeouts. You suspect a memory leak, and your heap dump analyzer has identified a few leak suspects in the Java heap space.
What next?
In this article we’ll look at some typical coding issues that result in memory leaks, and how to find and fix the problem.
What is a Memory Leak?
A memory leak occurs when a program unnecessarily allocates more and more memory over time. Eventually, the program slows down and finally throws an OutOfMemoryError.
Not all memory problems are leaks: they could be caused by either wasted memory, under-configuring the heap or other memory pools, or lack of RAM in the device or container. The best way to tell if the problem is a memory leak is to analyze the behavior of the garbage collector (GC).
The graphs below were produced by GCeasy, a GC log analyzer, showing heap usage over time. The red triangles indicate full GC events. The first graph shows a healthy GC pattern, whereas the second indicates a memory leak.
Fig: Healthy Memory Pattern vs Memory Leak Pattern
Notice that, although the GC is freeing memory, heap usage continues to increase over time, and the GC runs more and more frequently. As time goes on, GC events run back-to-back in an attempt to clear memory, and performance degrades drastically.
Do All Memory Leaks Occur in the Java Heap Space?
The majority of leaks affect the heap. For a debugging guide for heap memory issues, see this article: Java OutOfMemoryError: Heap Space.
It’s also possible for other areas, such as the metaspace or the direct buffer space, to suffer from memory leaks. In fact, there are nine different types of OutOfMemoryErrors in Java. We can distinguish the type of error by examining the error message.
How Do We Troubleshoot OutOfMemoryErrors?
To effectively find memory leaks we need to examine:
- The error message, to determine the error type;
- Garbage Collection logs, which can be analyzed by a tool such as GCeasy;
- A heap dump, which can be analyzed by a dump analyzer such as HeapHero or Eclipse MAT;
- The stack trace, which is displayed after the error message. This shows the line at which the error occurred, followed by the path the program took to reach that point.
The error message tells us which area of the JVM memory has run out of space. The GC logs indicate whether the issue is a memory leak, or just excess memory usage. If the problem lies with the Java heap space, we can use a heap dump analyzer to find and trace the ownership of the largest objects in memory.
For a demonstration of how to do this, it’s worth watching this video: How to Analyze a Heap Dump Fast.
Finally, working back through the stack trace helps us locate the offending code.
Typical Causes of Memory Leaks and How to Fix Them
In this section, we’ll look at a few of the causes of memory leaks, with hints on how to fix the code. Most, though not all, leaks will fit into these categories.
1. Forever-growing Object
This is one of the two most common symptoms of memory leaks. A collection or cache continues to grow over time, until it causes memory problems. Causes include:
- The program is not preventing duplicates from being stored. Make sure a proper policy is in place to prevent duplicates.
- The program doesn’t have a policy to evict older records from a cache, so the cache continually grows over time.
- A program loop is not terminating when it should, and contains code to add to a collection or cache. Use the stack trace to identify the loop, and ensure proper conditions are in place to make sure it terminates, even under unexpected conditions.
2. Proliferation of Same Type of Objects
This is the second most common symptom of leaks. The heap dump analysis will show a very large number of the same class of object. The causes can include:
- Objects are not being released to the garbage collector when they’re no longer needed. This may be because they’re defined in the wrong scope, because they are not set to null after use, or because the program fails to call the close() method on objects that have one.
- The program is creating a large number of threads that don’t terminate;
- Objects aren’t being re-used;
- The program uses a large number of string concatenations. This creates new strings each time. It’s better to use the StringBuffer() class, which allows strings to be edited within a single object.
- The program is looping without a proper termination condition.
3. Slow finalize() Methods
This is a less common cause of memory leaks. In fact, the finalize() method has been deprecated in later versions of Java, because it’s unreliable.
If you’re analyzing the dump with HeapHero, it includes a list of objects awaiting finalization.
When an object that has a finalize() method is eligible for garbage collection, the method is placed in the finalizer queue. This queue is single-threaded, so if this method is slow or hangs, none of the objects behind it in the queue can be garbage collected until it completes. This could be because it’s waiting for I/O or some other resource. To solve this issue, include a close() method in classes rather than a finalize() method.
4. Mutated keys in collections.
This is an obscure and hard-to-debug cause of memory leaks. Collections such as HashMaps use the key to determine the bucket where an entry is stored, and if the key is changed, the map is unable to find the entry. It therefore doesn’t protect against duplicates, and doesn’t successfully delete entries. To prevent this, always use the final modifier on variables used as keys so they can’t be changed.
5. Uncleared ThreadLocal Variables
This is another unusual and difficult-to-find error. ThreadLocal variables are used to associate a state with a thread. If they’re not cleared, and the thread doesn’t terminate for some time, these variables can build up in memory.
This is particularly likely to happen when we’re using thread pools.
To fix this, either ensure threads terminate as they should, or use the remove() method of the variable to free it.
Here are blogs that highlight the Common Memory Leaks and Not-So-Common Memory Leaks in Java that can be used in debugging and fixing memory leaks.
Conclusion
In this article, we’ve looked at how to find memory leaks, examined some of the common causes and included hints on how to fix them.
If you’ve experienced different causes of memory leaks, it would be great if you’d share them in the comments section, so we can pool our knowledge.
Thank you.
This content originally appeared on DEV Community and was authored by JillThornhill