As you already know, when a JVM language program creates a new object, a part of the heap memory is being used. What happens when there is no available heap memory left? How does the JVM know what parts of memory may be reused for something else? In this topic, we will tackle these questions and learn about garbage collection — a way to perform automatic memory management.
Memory management strategies
The heap memory, even if big enough for most programming tasks, is still limited, as it takes part of the physical memory (RAM) on the computer. So a program taking too much memory will eventually lead to crashes. Most programs have objects that at some point of the execution won't be used anymore, which means the memory may be freed and reused later.
Some programming languages (for example, C or C++) require programmers to allocate and free memory manually. Although this provides more control over the resources, manual memory management may be a challenging task, especially for beginners, resulting in errors and memory leaks.
The JVM utilizes automated memory management, which allows developers not to worry about memory while writing code and prevents possible programming mistakes. Memory is allocated in the JVM heap every time a program creates a new object and is freed using the garbage collection process.
What is Garbage Collection?
The Garbage Collector (or GC) is a part of the JVM that frees up the memory in runtime for further use. There are many different garbage collection algorithms and implementations, but their work may be simplified into two common steps. The first step is to determine which parts of memory the program no longer uses (i.e., "garbage"), and the second step is to free these parts of memory. Additionally, a compacting operation may be performed after the deletion step — all currently used objects are moved next to each other to free a big contiguous memory region and avoid fragmentation.
In order to identify garbage, the JVM goes through all the objects and checks whether they are still reachable in the program. All objects that can't be reached from the program or from other reachable objects are considered "garbage", and the corresponding memory is freed up.
Some algorithms take into account additional information about objects, for example, the time since the object was created. Such algorithms are called generational garbage collection algorithms. It has been noticed that most objects in programs are used only for a short time after creation. Thus, the garbage collector doesn't need to examine every object in the heap on every run and focuses mainly on recently created objects (the "younger generation"), which reduces the garbage collection time.
Running GC
Garbage collection is performed automatically while a program is running. The JVM handles all the dirty work itself, including deciding when to run the GC. It may happen, for example, in fixed time intervals or when there is no free heap memory left.
Usually, developers don't need to think about how the garbage collector works and how to customize it. The JVM is designed to determine the best time for garbage collection based on current resource usage and runtime conditions. However, you may "request" the JVM to perform garbage collection immediately in the following ways:
calling
System.gc();calling
Runtime.getRuntime().gc().
Making these calls is discouraged for several reason:
It's only a suggestion, not a command — the JVM treats
System.gc()as a hint. Thus, it may be ignored altogether, leading to unpredictable behavior.Forcing a garbage collection can interfere with the JVMs own optimizations and memory management heuristics;
It can trigger unpredictable “stop-the-world” pauses — most collectors halt all threads to reclaim memory, so invoking GC during critical operations risks long, unbounded delays that degrade application performance.
Instead of relying on manual GC calls, you should ensure to write memory-sensitive code. For example, when dealing with files or database connections, consider using Java’s try-with-resources or Kotlin’s use instead of manual close calls, so that resources are automatically closed when the block ends. Also, avoid unbounded static collections by removing references when they’re no longer needed or use a bounded cache with eviction (e.g., Guava Cache). This ensures that unused objects don’t linger and can be garbage-collected.
Example
Let's say that we have a code to check the used memory size (you can do it using Runtime.getRuntime().totalMemory() and Runtime.getRuntime().freeMemory()), and we put it in a program that works the following way:
Print the used memory information before performing any operations.
Create a bunch of new objects in a cycle for further use.
Print the used memory information after the objects' creation.
Perform the necessary operations (without creating new objects) so that the objects are no longer used in the code.
The value from step 3 will be greater than the value from step 1, since each new object takes some part of the available memory. After step 4, the objects become unreachable from the code and the memory may be freed up by calling System.gc() or Runtime.getRuntime().gc(). Printing the used memory information after the garbage collector invocation will show a value less than the value from step 3 if garbage collection was really performed.
Conclusion
The JVM performs automated memory management, protecting programmers from possible memory leaks and errors.
The Garbage Collector (GC) is a part of the JVM that identifies the unused "garbage" objects and cleans the corresponding memory regions to reuse them later.
The GC may be invoked manually in the code, but it is not guaranteed that the garbage collection will be performed, as the decision is still up to the JVM. This is not recommended.