Memory Leak

by examples from Java


26 Aug 2017 View Comments
#computer #programming #memory #leak #java #example

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:

1. Local scope as possible

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:

public static void main(String[] args) {
    int[] ints = new int[10000];
    for (int i = 0; i < ints.length; i++) {
        ints[i] = i;
    }
    condition = ints[20] == 1;
    // do some other stuffs unrelated to int array itself
    if(condition) {
        System.out.print("go");
    } else {
        System.out.print("no go");
    }
}

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:

public static void main(String[] args) {
    boolean condition = addElementsAndDoActions();
    // do some other stuffs unrelated to int array itself
    if(condition) {
        System.out.print("go");
    } else {
        System.out.print("no go");
    }
}

private static boolean addElementsAndDoActions() {
    int[] ints = new int[10000];
    for (int i = 0; i < ints.length; i++) {
        ints[i] = i;
    }
    return ints[20] == 1;
}

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.

2. open connections

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:

try
{
  Connection conn = DriverManager.getConnection();
  ...
  conn.close();
}
Catch(exception ex)
{
}

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:

try
{
  Connection conn = DriverManager.getConnection();
  ...
}
Catch(exception ex)
{
}
finally {
    conn.close(); //ensure close is always called.
}

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.

3. Cache

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:

public class Key {
    private String name;
    public Key(String name)
    {
        this.name=name;
    }
    public static void main(String[] args) {
        Map<Object, Object> cache = new HashMap<>();
        while (true) {
            cache.put(new Key("key"), "value");
        }
    }
}

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:

public class Key {
    private String name;
    public Key(String name)
    {
        this.name=name;
    }
    @Override
    public boolean equals(Object o) {
        if (this == o) { //ref check to see if they are indeed the same
            return true;
        }
        if (o == null || getClass() != o.getClass()) { // basic checks
            return false;
        }
        test test = (test) o;
        return name != null ? name.equals(test.name) : test.name == null; // see if they have the same name
    }
    @Override
    public int hashCode() {
        return name != null ? name.hashCode() : 0;
    }

    public static void main(String[] args) {
        Map<Object, Object> cache = new HashMap<>();
        while (true) {
            cache.put(new Key("key"), "value");
        }
    }
}

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 equals() and hashCode() together (always together!) especially when they are dealing with any kind of hash-based collections.

Detecting the memory leak through VisualVM

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.

Share this post

Me

I am a passionate programmer working in Vancouver. I strongly believe in art of algorithms and together with it to write clean and efficient software to build awesome products. If you would like to connect with me, choose one from below options :) You can also send me an email at