Java has been one of the most versatile programming languages in almost every field of programming since its launch more than two decades ago. As a Java programmer, it is considered that one does not need to think about how memory management works, people find it unnecessary and boring. This is because of the automatic memory management present in Java.
Java has an automatic garbage collector working in the background which takes care of allocating and freeing up memory. This feature was missing in the traditional programming languages like C/C++ where you must manually take care of memory management.
Although this process is automatic in Java, Java applications are very much prone to memory-related issues like memory leaks. So, some insights on how memory management works can come in handy for improving performance, detecting or preventing memory leaks and learning better coding practices. The whole concept of memory management may seem a bit unnerving initially, but once you start looking into it, you might start to enjoy fixing these memory leaks.
One might ask, why should we even worry about memory leaks if Java boasts about having an Automatic Garbage Collector (GC), which is specifically designed to tackle such situations? This raises certain questions about the effectiveness of the GC.
However, being one of the grand accomplishments of Java, GC works concisely. It handles majority of the memory, but it is our own programming mistakes that sometimes keep the GC from freeing up unnecessary chunks of memory in a timely manner resulting in memory leaks.
In this blog, I will acquaint you with how GC works, memory leaks and their severity, detecting memory leaks, and common memory leaks scenarios. Let’s dive in!
How Garbage Collector Works:
Java uses Heap Space to allocate memory to the new Objects. Heap space mainly has two partitions:
Minor GC:
Major GC:
The Major Collection takes time, while Minor collection is a quick process, this accounts for the performance of the application. In general, the less the GC runs, the better is the performance.
Allow me to explain the severity of memory leaks.
Memory Leak is a situation where the GC is unable to detect and release an Object which is not being used by the application. Every Object has a designated time, and it is meant to leave the memory after it has served its purpose, but in some cases due to our programming mistakes, that object is referenced directly or indirectly and is unable to leave the system.
So basically, it keeps holding assignable memory to a newer object that is actively being used by the application. Accumulation of such objects results in poor utilization of the Heap Space and could result in OutOfMemoryError.
Not all memory leaks are serious, most memory leaks have minimum impact on your application’s performance and can be ignored, but there could be some memory leaks that are severe and causes a significant impact on the performance.
How severe memory leaks affect the application:
Now that you are familiar with memory leaks, allow me to explain how they can be detected.
1. Usage of Static fields: Once the static variable is declared, it exists till the program is running. That means it will not be Garbage Collected until there are no instances of the corresponding class being run in the JVM.
Example:
How to prevent: You should use the static fields very carefully.
2. Unclosed DB Connections: When using the DB connection it is necessary that after it has served its purpose it should be closed using the close() method. Unclosed connections can cause memory leak:
Example:
How to prevent: In the above example, we can add a finally {} block to close() the connection. In general, it is a best practice to close the connections on your own without depending on JAVA to close them for you.
3. Unclosed Streams:
Like unclosed connections, having reference to an unclosed file or network stream can also be a cause of memory leaks.
Here if the input file is large and the connection is not closed, the GC will take time to realize that this stream needs to be closed.
How to prevent: You should always close the stream after its use. Also close the stream in a final block similar to the above example, as the close() function in the try block may not be triggered in case of an exception.
4. Wrong Implementation of hashCode() and equals():
Another common scenario where memory leaks occur is while using hash maps. Here if one uses as a key, an object of a class that does not implement hashCode() and equals(), or implementing these in an improper way can cause memory leak occurs.
Example:
Here, we have class Key without hashCode() and equals() implementation. Now we will try to insert duplicates into a Map using this Key class.
In the above code, we are trying to add duplicates to the Map, since duplicates are not allowed in Map only one entry should be inserted.
But as we have not implemented the equals() and hashCode() methods, these entries will not be considered the same and duplicates will be added into the Map. Such duplicates pile up to increase the memory usage.
If two objects return the same hash code, then these two objects must be equal according to the equal(Object) method.
How to prevent: To prevent such scenarios, one should always make sure to override hashCode() and equals() methods properly.
For the above example, equals() and hashCode() methods can be overridden as shown below.
5. Changing the properties of an object used as a Map Key:
Changing a property of an object which is used as a Map Key could lead to some issues while retrieving it.
Our hashCode() implementation makes use of this name property to calculate the hashCode as shown in the below code snippet.
Let us say we use an object as a key and put this key associated with a value in a map. Later we change the value of the name property of this object.
After changing the name property, if we try to retrieve the object using Map.get() it will only return NULL, as we have changed the very property to calculate the hashCode for the object.
This is a perfect example of a memory leak as the application may never find that entry, but the Map will continue to hold its reference.
How to prevent: To avoid such issues, it is recommended to use immutable classes as keys.
Although Java has a Garbage Collector that offloads a great deal of memory management job from the programmer’s shoulders, it is still very much prone to memory leaks and there is still room for improvement. So, until those improvements are made, a Java programmer must know detailed practical aspects of memory allocation.
Write to us with your thoughts about this blog and visit us at Nitor Infotech to know more about how we partner with businesses to smoothly navigate their digital transformation journeys!
Subscribe to our fortnightly newsletter!