×
Shreyash Bhoyar
Senior Software Engineer
Shreyash is an experienced Senior Software Engineer with a demonstrated history of working in the information technology and services indust... Read More

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.

Discover how Java distinguishes itself from Node.js and .NET and can enhance your software engineering journey.

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:

  1. Young Generation (Space allocated to new Objects)
  2. Old Generation (Space reserved for Objects that survive the minor GC)

Minor GC: 

  1. Minor GC is triggered when the younger generation space is full, and JAVA is unable to allocate memory to new objects.
  2. In Minor GC the memory allocated to the Unreferenced Objects from Young Generation Space is reclaimed.
  3. If a certain Object Survives a few cycles of minor GC, it is transferred to the Old Generation Space.
  4. Minor GC occurs frequently and is fast because the young generation space is relatively smaller and comprised of short-lived objects which no longer have any reference.

Major GC:

  1. Major GC is triggered when the Old Generation Space is full, and the space cannot be allocated to the objects surviving the Minor GC. So, it can be said that most of the Major GC’s are triggered by the Minor GC’s.
  2. In Major GC the memory allocated to the Unreferenced Objects from Old Generation are reclaimed.
  3. The Old generation space is larger than the young generation space and takes a long to fill, hence the GC on this space is infrequent and is relatively slower. The other reason is that different algorithms are used in both scenarios, the former being time-efficient and the latter being space efficient.

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 Leaks and their Severity:

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.

Memory Leaks and their Severity 1 Nitor Infotech

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:

  1. Firstly, the Minor GC occurs frequently in an attempt to reclaim the memory.
  2. If the application has severe memory leaks the Minor GC is unable to reclaim the memory used by the objects causing these memory leaks, these objects are promoted to the Older Generation space.
  3. Because of this the older generation space starts filling up rapidly and causes Major GC to occur.
  4. This Major GC can pause the application resulting in an Evident Performance Delay.
  5. This burdens the application to a point where the Major GC occurs more frequently and causes the application to CRASH as there is simply no memory left to allocate the new objects.

Now that you are familiar with memory leaks, allow me to explain how they can be detected.

Here are various ways by which you can detect memory leaks in Java:

  1. Various tools are available that monitors memory usage. Although it won’t show exactly what the cause of memory leak is, it can point you to the part of your application that is consuming most of the memory. Having these details are a great start in finding the memory leak.
  2. Using Heap Dump. Heap dumps contain a snapshot of all the live objects that are being used by a running Java application on the Java heap. These snapshots can be used to detect memory leaks
  3. Enabling the GC logs, These GC logs provide all the information you need to see how the memory cleanup works and provide the time each cycle has taken, this time indicates for what duration the application was stalled to clean the memory. Higher values for this mean either the memory is not sufficient for your application to properly perform its tasks, or you have a memory leak.
  4. Analyzing the application performance to find any evident symptoms of memory leaks such as below:
    • OutOfMemoryError errors in the logs
    • Application not working as expected
    • Application Crashing without any reasons.
    • Unnecessary delay in processing

Common Memory Leaks Scenarios:

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:

  • In the above example, we are storing a large collection in an object declared as Static. Hence even after coming out of the insert() method, the list will not be garbage collected as we know the lifetime of the static field and the program is same.
  • If we just drop the static keyword from the above program, as soon as it comes out of the insert() method the list object will be eligible to be garbage collected.

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:

  • In the above example, if in case an exception occurs, the connection will never close.
  • Here the Garbage Collector won’t be able to free the connection object as The JDBC driver carries a reference to connection unless you close().

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.

Some ways to make your class immutable:

  1. Declare the data members in the class as private so that direct access is not allowed. Also, make sure that there are no setter methods in the class.
  2. Declare the data members as final so that their values cannot be changed after the object is created.
  3. A class should be declared as final to restrict the creation of child classes.

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 image

Subscribe to our
fortnightly newsletter!

we'll keep you in the loop with everything that's trending in the tech world.

We use cookies to ensure that we give you the best experience on our website. If you continue to use this site we will assume that you are happy with it. Accept Cookie policy