Memory leak is one of the great concerns of java developers.Basically, memory leak is the outcome of the failure of the Garbage Collector(GC) to detect unused java objects and recover the used spaces by these garbage objects.The unused objects are not detected by GC as they are still referenced due to coding flaws.
Here we will discuss some of the flaws in our coding styles that leads to memory leaks and also we will look into the possible solutions.
1.ThreadLocal Variables:ThreadLocal variables are one the main culprits for memory leaks. Threadlocal variables are bind to respective threads which means that the lifetime of these variables are linked to lifetime of the thread.These variables will be garbage collected only if the parent thread is garbage collected.
In web serves and application servers like Tomcat,WebSphere etc., threads are managed by using thread pools i.e., collection of worker threads.
For instance, to cater to a http request, a free worker thread is used from the thread pool. If the servlet or any other java class creates ThreadLocal variables during processing of the request , but does not remove the thread pool.Now the worker thread is pulled back to the pool to handle other requests and the ThreadLocal variable still remains attached to that worker thread.As the worker thread is still alive so the ThreadLocal variable is not garbage collected.This leads to memory leak and may result in OutOfMemoryError.
Solution: Remove the ThreadLocal variable from the worker thread once the purpose of adding the ThreadLocal is served.
2. Mutable static fields and Collections:Static fields are never garbage collected. Mutable static fields especially collections need to be cleaned up explicitly. We need to make sure that we remove the unused objects from mutable static collections. Otherwise it may lead to memory leaks.This is one of the most common scenario of memory leak.
Solution: As per programming guidelines,mutable static collections should not be used in our programs.Please look for some other alternatives, if you need any collections at the class level.
3. Wrong implementation of equals and hashcode:If our equals/hashcode methods violate the equals contract it will lead to memory leaks when used as a key in a hashed based collection.
Also if we do not override equals and hashcode in our class, we will also face the issue of memory leak as the default hashcode and equals is based on object identity.This means that every object will have different hashcode and also no two objects will be equal.
For instance in HashMap, while storing/retrieving the entry,the hashcode is used to find the bucket in which the key is stored and the equals is then used to identify the object from the bucket.Using an object without a valid hashcode/equals implementation as a key in a map, we will be able to add things but we will not find them anymore as the hashcode will not match. So we will not be able to remove the entry from the hashMap.Even worse if we re-add it, it will not overwrite the old item but really add a new one.The Map will just go on growing and this will eventually result in memory leak.
Solution:Properly implement equals() and hashCode() for the classes whose object is meant to be used as the key in a hashed based collection like HashMap.Also religiously follow the equals/hashcode contract.
4.Class Loading leaks: Java classes are loaded in method area(Permanent generation-PermGen) of JVM.If this area is full, no more classes can be loaded and an out-of-memory error occurs in the PermGen. We will discuss some of the common class loading issues.
- Large Classes
Depending on the number of fields and class constants, the memory needed to load the class varies. Quite often the root cause of large classes is too many static constants.
Solution:Split large classes into several smaller ones, especially if you know that not all of the constants are needed at the same time. The same is true for class members. If you have a class with 20 members, but depending on the use case you use only a subset, it makes sense to split the class. Unused members still increase the size of the class.
- Classloader Cannot Be Garbage-Collected
A classloader will be removed by the garbage collector only if nothing else refers to it. All classes hold a reference to their classloader and all objects hold references to their classes. As a result, if an application gets unloaded but one of its objects is still being held (e.g., by a cache or a thread-local variable), the underlying classloader cannot not be removed by the garbage collector.
Solution: Restart the application/web server in which the java application is deployed.
5. Native Memory Leaks:
Java Native Interface (JNI) enables Java code running in a Java Virtual Machine (JVM) to call and be called by native applications and libraries written in other languages such as C, C++ etc.
Every java object created in a native method begins its life as a so called local reference. That means that the object is referenced until the native method returns. We could say the native method references the java object. So you don’t have a problem unless the native method runs forever. In some cases you want to keep the created object even after the native call has ended. To achieve this you can either ensure that it is referenced by some other java object or you can change the local reference into a global reference. A global reference is a GC root and will never be garbage collected until explicitly deleted by the native code. The only way to discover such a memory leak is to use a heap dump tool that explicitly shows global native references. If you have to use JNI you should rather make sure that you reference these objects normally and forgo global references altogether.