One of the major advantages of Java since its first version was that developers didn’t have to worry about memory, as Java itself was able to keep it clean and free memory automatically. But any good Java developer should know the basics on how Java handles memory to be prevent memory leaks and bottlenecks.
To begin with, Java divides memory into two distinct segments:
- Heap: instances, variables, …
- Non-Heap/Perm: code, metadata,…
As the first step to optimize memory in Java, we should focus on the Heap, as it is what we can “control”. The Heap is divided in two generations depending on their lifetime:
- Young Generation
- Old Generation
Usually the Young generation is composed of local variables and temporary objects. While the older generation contains structures that are needed during the execution like configurations.
The younger generation is divided into two:
- Eden: This is where objects are created initially
- Survivor: It’s like the limbo through which we pass from the Young to the Old generation.
The Garbage Collector
The Garbage Collector is the system making sure the memory is clean. It performs two types of periodic tasks:
- Minor Collection: Reviews quickly the younger generation.
- Major Collection: Reviews all the memory, mainly the older generation.
The garbage collector runs at the same time as the normal program execution. Each execution involves a small pause (usually milliseconds) in all the threads that are running at that time. While your application memory remains healthy, the Garbage Collector will limit its actions to minor collections, to not interfere with the flow of the application.
Different memory strategies
There are several implementations of the garbage collector, being the most common the
Serial Collector. This implementation, as well as being the simplest, uses only one processor. If you’re using a more powerful machine with multiple processors and a good amount of physical memory, you can activate the
Parallel Collector , which uses multiple CPUs at the same time. This improves the way in which the garbage collector works. It can also parallelize the flow of normal execution of the application.
For proper operation and cleaning of memory, we should have little short-lived temporary objects better than long, durable objects. The small temporary objects will stay in the Eden, so they will be collected much earlier and much faster.
Also, having unused objects in memory, although they do not disrupt the execution of the program, will slow the execution of the garbage collection. Because you have to process them over and over again to check if they can be deleted.
At some point it may seem tempting to force a Garbage Collector implementation calling System.gc(). However, this will force a major collection asynchronously, breaking up all the heuristics of the Garbage Collector and stopping your application while it lasts. It is so discouraged that there is an option in the virtual machine to disable these calls: -XX:+DisableExplicitGC
- Weak : It does not prevent the GC to clean it.
- Soft : The GC respects a little and removes the instance only if memory is needed. Useful for caching, but can be misleading.
- Phantom: Always returns null. The link doesn’t really point to the object. Can be used to clear instances before taking the object that binds it.
For example we can use WeakHashMap, which works as a HashMap, but using weak references. So, if the key contains an object which is only referenced in the map, it is no longer considered useful and is removed.