Before we had an automatic garbage collector, it was easily missed for the application to release the object references properly. Those days people had to manually release the objects spaces and make sure the application has good integrity by reading and examining the codes carefully. With modern languages like Java, it comes with the “garbage collector” which remove/clean objects no longer being used by the application automatically. The garbage collector is quite smart and handy because in most cases, we do not have to worry about the application leaking loads of memory. However, even with the garbage collection, it is still not guaranteed unused objects will always be released/removed. Sometimes we need to make sure the garbage collector would clear these objects. Let’s take a look at some of these examples:
This topic isn’t really about leaking memory but more about holding onto the memory unnecessarily. In one of Robert Martin’s books, Clean Code, he discusses that the function should do only one thing. The book then describes the main reason doing so is to have code through cohesion and coupling which makes the overall code highly reusable. Making reusable code is very important to make the overall program flexible and adaptable. By following this rule, you can also make sure the memory is safely cleared before you do something else. The garbage collector can then collect these obsolete objects when more memory space is required. Here is an example of what I mean:
We have a simple block of code inserting some random values just for the illustrating the problem. Above function will load up the int array into the memory. Then it will not be released or cleared until the entire function ends. This isn’t quite ideal because this int array isn’t required by the following loop. We can break down above function like following:
Now if we separated out the insertion and its action associated with the ints array, the garbage collector can then release memory block whenever it requires it since the int array itself is locally defined to its separate function.
You should close with “finally” when closing any kind of connections or streams to make sure they are always closed when exceptions are encountered. Let’s go straight into the example:
In this example, there is a possibility an exception occurring before it even gets a chance to make
conn.close() call. Yikes, Memory Leak! The connection will likely stay open. In a situation like this, we can generally close the connection manually inside finally block, like this:
But what if
conn is null? we might encounter an NPE then. We can simply just check for whether the
conn is null. Or you can replace
conn.close(); call with something like
DbUtils from the apache commons and call
DbUtils.closequietly(conn); instead. This will ensure to close a connection, handles conn being null and hide any exceptions that may occur. A similar concept applies to the streams. Instead of DbUtils.closequietly, you can instead do
IOUtils.closequietly(stream), provided from the apache commons as well. Also, I would like to mention that starting in Java SE 7 and later, you can try-with-resources. This will also ensure stuffs that needs to be closed will be closed. You can declare as many as resources that require closing at the end and this try-with-resources are just like any other try block that would support catch and finally as well.
When the cache is implemented using the hash-based data structure like a Map with a key that doesn’t have any overrides for equals() and hashCode(), you are bound to leak the memory easily because the size of the hash will continue to grow regardless of what you put in as a key. Example:
Since Key object do not have equals() and hashCode() overridden, this code will do nothing but inserting new map entry into the map each time it loops. hashmap has no way of knowing if the 2 different referenced objects are equal or containing the same hashcode. What you need here is implementing the proper equals() and hashCode(), like following:
Let me describe what is happening behind the scene.
cache.put will try to get the
hashCode() for the item to place in the aka
bucketing. If the hashCode produces the same, that means there is already an item in that bucket. So the code runs
equals() method to see if the object inserting is truly the same object. When they are indeed equal, the same item will be overwritten, otherwise, a new item will be created. To note, internally equals will be running for all the items in the cache which has the same hashCode (bucket number) but you really don’t need to know this further. Anyways, as a general contract for Java developers, it is very important to override both
hashCode() together (always together!) especially when they are dealing with any kind of hash-based collections.
VisualVM is one of the best and effective tools I used for monitoring the memory/CPU usage of the application. VisualVM is a GUI profiling/monitoring tool provided by Oracle JDK. It provides nice visual monitoring graphs of memory/CPU usage, visualizing threads, basic profiling capabilities, provides thread dumps, analyzes core dumps and can browse through the heap dump and probably do more than what I listed here. This tool obviously isn’t the solution to all the memory leaks out there, but it will surely spot the obvious ones. I would recommend running this at least once for your application to see how it is behaving.