Memory leak in Java is the major problem for the programmers. They always want to get rid of this problem. Here in this article we will share with you the best tips on how to identify memory leaks in Java. And share with you the best techniques and methods to avoid them.
The JVM and the Java garbage collector play a crucial role in Java memory leaks. Because both of these tools are responsible for the memory allocation and deallocation in Java. Both of these tools are playing crucial roles in Java. But the Java applications are still facing the memory leak problems. Let’s have a look at what are memory leaks in java.
What is a Memory Leak in Java?
The memory leak occurs when the objects of the code are no longer being used by the application. And the garbage collector is also not able to remove that object from the working memory.. Because the object is having the reference in the code. In this way the application demands more memory to run the new process. And doesn’t release the memory it leads to the fatal OutofMemory Error.
There are two types of objects i.e. referenced and unreferenced. The Garbage Collector is designed to remove those objects that are not being unreferenced. Thus it can remove the unreferenced object from the memory. But it can’t remove the referenced either they are in use or not by the applications.
How to find memory leak in java
How to identify memory leaks in Java is quite difficult for the programmer. There are a large number of tools in the world that perform the statistics analysis to identify the potential memory leaks in Java. But these techniques aren’t perfect for the programmer because they need to detect the problem on the running system. Here are some of the methods that will help you to prevent memory leaks in Java with some scenarios that you may also face.
In this introductory section, we’ll focus on the classic memory leak scenario – where Java objects are continuously created without being released.
An advantageous technique for understanding these situations is to make rendering a memory leak easier by setting a smaller size to Heap. Therefore, when we start our application, we can adjust JVM to our memory needs:
These parameters specify the original Java Heap size as well as the maximum Heap size.
The first scenario that could cause a Java memory leak refers to a heavy object with a static field.
We created our ArrayList as a static field – which will never be collected by JVM Garbage Collector during the lifetime of the JVM process, even after the calculations it was used for have been made. We also called on Thread.sleep (10000) to allow GC to perform a full collection and try to recover everything that can be restored.
Notice how the entire memory is naturally free at first.
Then run and perform the iteration process in just 2 seconds – loading everything in the list (of course this depends on the machine you are running the test on).
A full waste collection cycle is then triggered, and the test continues to be performed to give this cycle time to run and finish. As you can see, the list will not be restored, and memory usage will not decrease.
Now let’s see the exact same example, only this time do not refer to a static variable ArrayList. Instead, it is a local variable that is created, used, and then discarded:
When the method is finished with its job, we monitor the extensive GC collection around 50. second in the picture below:
Note how GC is now able to regain some of the memory used by JVM.
How to prevent it?
Now that you understand the scenario, there are distinct ways to prevent it from occurring.
First, we must pay close attention to our use of static; declares any assembly or heavy object that statically binds its life cycle to the actual life cycle of JVM and makes the entire object graph impossible to collect.
We also need to pay attention to collections in general – it’s a common way to hold on to references for longer than we need inadvertently.
2.2. Calling String.intern() on Long String
The second group of scenarios, which often cause memory leaks, involves string operations – specifically the String.intern () API.
Here we simply try to load a large text file in running memory and then return a canonical form using .intern ().
The internal API will place str String in the JVM memory pool – where it cannot be assembled – and again, this will cause GC not to release enough memory:
We can clearly see that in the first 15 seconds the JVM is stable, then we load the file and JVM performs waste collection (20th second).
Eventually, str.intern () is called upon, which leads to memory leakage – the stable line that indicates great use of memory that will never be released.
How to prevent it?
Keep in mind that interned string objects are stored in the PermGen space – if our application is designed to perform many operations on large strings, we may be able to increase the size of the permanent generation:
Finally, there are also several options to avoid the .internal () API on strings too.
2.3. Unclosed Streams
Forgetting to close a stream is a very common scenario, and certainly, one that most developers can relate to. The issue was partially removed in Java 7 when the ability to close all types of streams automatically was introduced in the try-with-resource clause.
Why in part? Because the syntax with try-with-resources is optional:
Let’s see what the program’s memory looks like when you load a large file from a URL:
As we can see, the use of heaps gradually increases over time – which is the direct impact of the memory leak caused by not shutting down the stream.
Let’s dig a little deeper into this scenario because it’s not as clear as the rest. Technically, a non-closed stream will result in two types of leaks – a low-level resource leak and memory leakage.
Low-level resource leakage is simply leaking of an OS-level resource – such as file descriptions, open connections, etc. These resources can also leak, just as memory does.
Of course, JVM uses memory to keep track of these underlying resources too, which is why it also results in a memory leak.
How to prevent it?
We always have to remember to close streams manually or to use the auto-close feature that is introduced in Java 8:
In this case, BufferedReader is automatically closed at the end of the try declaration without the need to close it in an explicitly final block.
2.4. Unclosed Connections
This scenario is similar to the previous one, with the primary difference in handling unclosed connections (e.g. To a database, to an FTP server, etc.). Again, incorrect implementation can do much damage, which can lead to memory problems.
The URL connection remains open and the result is predictably a memory leak:
Note how Garbage Collector can not do anything to release unused but referred to memory. The situation is immediately clear after the first 1 January 2004. minute – the number of GC operations decreases rapidly, causing increased Heap memory use, leading to OutOfMemoryError.
How to prevent it?
The answer here is simple – we have to always close connections in a disciplined way.
2.5. Adding Objects with no hashCode() and equals() into a HashSet
A simple but ubiquitous example that can lead to a memory leak is to use a HashSet with objects that are missing their hashCode () or straight () implementations.
When we start adding duplicate objects in a set – this will only ever grow instead of ignoring duplicates as it should. We will also not be able to remove these objects once they are added.
Let’s create a simple class without either corpse or hashCode:
Now, let’s see the scenario:
This simple implementation will lead to the following scenario at runtime:
Note how the waste collector stopped being able to regain memory around 1:40 and note that the memory is leaking; the number of GC joints decreased almost four times immediately afterwards.
How is it prevented?
In these situations, the solution is simple – it is important to provide hashCode () and equal () implementations.
One tool worth mentioning here is Project Lombok – this provides much standard implementation for comments, for example. @EqualsAndHashCode.
3. How to Find Leaking Sources in Your Application
Diagnosing memory leaks is a lengthy process that requires a lot of hands-on experience, troubleshooting, and detailed knowledge of the application.
Let’s see what techniques can help you beyond standard profiling.
3.1. Verbose Garbage Collection
One of the fastest ways to identify a memory leak is to activate proper waste collection.
By adding the -verbose: GC parameter to the JVM configuration of our application, we enable a very detailed trace of GC. Summary reports appear in the default error output file to help you understand how your memory is managed.
3.2. Do Profiling
The other technique is the one we have used throughout this article – and that is profiling. The most popular profile is Visual VM – which is a great place to start moving past JDK tools on the command line and into lightweight profiling.
In this article, we used other profiles – YourKit – which have some additional, more advanced features compared to Visual VM.
3.3. Review Your Code
Finally, this is more of general good practice than a particular technique for dealing with memory leaks.
Put – review your code thoroughly, practice regular code reviews, and use well of static analytics tools to help you understand your code and system.
Now we have seen that what is memory leak in Java and how can we prevent it. Follow all the tips mentioned above to prevent memory leaks in Java.