Java Memory Leaks and How to Avoid Them
Java is a prevalently used object-oriented programming language. Java released by Sun Microsystems in the year 1995. One major advantage of developing software with Java is it’s portability. When the language was invented, the primary goal was to be able to “write once, run anywhere.” The rules and syntax of Java are based on the C and C++ languages. Java Garbage Collection is a very important process for memory management.
Garbage Collection
Java Garbage Collection is a form of automatic memory management. In languages like C and C++, the programmer is responsible for both the creation and destruction of objects. Sometimes, the programmer may forget to destroy useless objects, and the memory allocated to them is not released. The used memory of the system keeps on growing and eventually there is no memory left in the system to allocate. Such applications suffer from “memory leaks”. After a certain point, sufficient memory is not available for creation of new objects, and the entire program terminates abnormally due to OutOfMemoryErrors. You can use methods like free()
in C, and delete()
in C++ to perform Garbage Collection.
In Java, garbage collection happens automatically during the lifetime of a program. This eliminates the need to de-allocate memory and therefore avoids memory leaks. Java programs compile to bytecode that can be run on a Java Virtual Machine. When Java programs run on the JVM, objects are created on the heap, which is a portion of memory dedicated to the program. Eventually, some objects will no longer be needed. The garbage collector finds these unused objects and deletes them to free up memory. One of the core benefits of Java is the automated memory management with the help of the built-in Garbage Collector. The Garbage Collector implicitly takes care of allocating and freeing up memory and thus is capable of handling the majority of the memory leak issues. While the Garbage Collector effectively handles a good portion of memory, it doesn’t guarantee a foolproof solution to memory leaking. The GC is pretty smart, but not flawless. Memory leaks can still sneak up even in applications of a conscientious developer.
Java Memory Management
JVM divides memory into two parts , stack and heap memory. Whenever we declare new variables and objects, call new method, declare a String or perform similar operations, JVM designates memory to these operations from either Stack Memory or Heap Space. When we talk about garbage collection in Java we meant Heap memory. Java Stack memory doesn’t garbage collected. Heap space in Java is used for dynamic memory allocation for Java objects and JRE classes at the runtime. New objects are always created in heap space and the references to this objects are stored in stack memory.
Heap Memory Model

Young Generation
Eden
- Eden is where all objects are allocated and aged. Eden covers the majority of the young generation. A minor Garbage collection occurs when this fills up.
- When the young generation is cleared during garbage collection, all objects in Eden either moved or discarded. Objects which are not in use can be discarded and free up space. Objects in use are moved either to one of the survivor spaces or to the old generation. Since all objects are moved or discarded, the young generation is automatically compacted when it is garbage collected.
Survivor Spaces
- Survivor spaces are designed to allow objects to remain in the young generation for a few Garbage Collection cycles. This increases the probability the object will be freed before it is moved to the old generation.
Old Generation ( Tenured )
- Objects that are long-lived are eventually moved from the Young Generation to the Old Generation. When objects are garbage collected from the Old Generation, it is a major Garbage Collection event.
Metaspace
- When the JVM loads classes, it must store metadata about those classes. This metadata stored in a separate native heap space called the metaspace. Metadata such as classes and methods are stored in the Classes that are no longer in use may be garbage collected from the Metaspace.
- Garbage Collection is automatically triggered when the class metadata usage reaches it’s maximum Metaspace size.
There was also another space called Permanent Generation before Java 8. The biggest disadvantage of Permanent Generation is that it contains a limited size which leads to an OutOfMemoryError. The default size of Permanent Generation memory is 64 MB on 32-bit JVM and 82 MB on the 64-bit version. Due to this, JVM had to change the size of this memory by frequently performing Garbage Collection which is a costly operation. Java also allows to manually change the size of the PermGen memory. However, the PermGen space cannot be made to auto increase. So, it is hard to tune it. Due to the this problems, Permanent Generation has been completely removed in Java 8. Metaspace has been introduced in the place of Permanent Generation. Metaspace grows automatically by default.
Garbage Collector Algorithm
There are different Garbage Collection algorithms. But their goals are same. Garbage Collectors typically have the following goals :
- Very short “stop the world pauses” with a target of a few milliseconds
- Pause times do not increase with a heap, live-set, or root-set size
- To handle heap sizes ranging from few MBs up to many TBs
- High concurrency — All heavy lifting work is done while Java threads continue to execute
- High Throughput
Throughput (Parallel) GC is the default Garbage Collector algorithm in the Java 8. This algorithm uses mark-copy in the Young Generation and mark-sweep-compact in the Old Generation. Both Young and Old collections trigger stop-the-world events, stopping all application threads to perform garbage collection. Both collectors run marking and copying/compacting phases using multiple threads, hence the name ‘Parallel’. Parallel Garbage Collector is suitable for multi-core machines. Mark and Sweep Model has two major phases.
- Mark : Identify and mark all object references that are still used and reachable, and the rest is considered as garbage.
- Sweep : Traverse Heap memory and find unoccupied spaces between the live objects, these spaces are recorded in a free list and are made available for future object allocation.
Dereference an Object in Java
The main objective of Garbage Collection is to free heap memory by destroying the objects that don’t contain a reference. When there are no references to an object, it is assumed to be dead and no longer needed. So the memory occupied by the object can be reclaimed.
There are various ways in which the references to an object can be released to make it a candidate for Garbage Collection. Some of them are:
- Making reference null
User user = new User();
user = null;
- Assigning a reference to other
User userOne = new User();
User userTwo = new User();
userOne = userTwo;
- Using an anonymous object
register(new User());
Memory Leak
A Memory Leak is a situation when there are objects present in the heap that are no longer used, but the garbage collector is unable to remove them from heap memory and because of that they are unnecessarily maintained.
The garbage collector removes unreferenced objects periodically, but it never collects the objects that are still being referenced. This is where memory leaks can occur.

There are some cases which can cause memory leaks such as : static fields, calling string.intern() on a lengthy string, unclosed streams and unclosed connections.
Static Field Holding an Object Reference
Static fields ties it’s lifecycle to the lifecycle of the JVM itself, and makes the entire object impossible to collect.
class MemoryLeakClass {
static final ArrayList<Double> list = new ArrayList<Double>(1000000);
}
Calling String.intern() on Lengthy String
Calling String.intern() on lengthy String causes memory leak because interned Strings stored in PermGen space.
String longString = readStringFromFile();
longString.intern();
As I mentioned earlier, Java 8 PermGen space replaced by Metaspace. With Metaspace using String.intern() won’t lead to OutOfMemoryError.
Unclosed Streams
Forgetting to close a stream is a very common scenario. The problem was partially removed in Java 7 when the ability to automatically close all types of streams was introduced into the try-with-resource clause. The problem with this feature was that it was optional.
To prevent this issue we always need to remember to close streams manually or we can use auto-close feature introduced in Java 8.
try (BufferedReader br = new BufferedReader(
new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
// some code
} catch (IOException e) {
e.printStackTrace();
}
In this case with Java 8, the BufferedReader will be automatically closed at the end of the trystatement, without the need to close it in an explicit finally block.
Unclosed Connections
This scenario is quite similar to the previous one.
try {
Connection conn = ConnectionFactory.getConnection();
} catch (Exception e) {
e.printStacktrace();
}
We always need to remember close connections in disciplined manner.
Thank you for reading my article about Memory Leak. I hope the contents elaborated here helped you in widening your knowledge base.