A Brief Overview of Garbage Collection in Java

Garbage collection, or the concept of essentially “cleaning up” no longer referenced spots in memory, is something that we take for granted when writing code in Java since this is more or less done for us by the Java Virtual Machine (JVM). Languages like C and C++ on the other hand require that the developer also handle their own garbage clean up. Some argue that handling your own garbage collection gives you maximum control, while others argue that a developer’s time can be better spent elsewhere if the language already takes care of this for you, especially if system memory is no longer something that was very limited in years prior.

“Who’s going to pick up this mess?”

At a high level, automatic garbage collection in Java works by:

  • Reference Counting: The number of references to a given spot in memory are tracked.
  • Mark/Sweep: Objects that have 0 references are “swept” away, whereas objects that have 1 or more references to them remain.
  • Copying: Objects that still have references are copied from one memory space to another, leaving behind the old space for reuse.
  • Generational: This technique divides the heap into generations (young, old, and permanent) and applies different collection strategies to each generation.

So what constitutes an object as young, old, or permanent?

  • Young: Objects that have newly been created and haven’t yet gone through one garbage clean up cycle.
  • Old: These are objects that have survived multiple garbage collection cycles and are considered therefore to be “old”.
  • Permanent: Generally, classes and methods are considered to be permanent spots of memory in a Java application.

But there’s a catch when it comes to how automatic garbage collection works in Java: there’s no guarantee when it will run, and there’s multiple reasons why this is:

  • Dynamic nature of memory usage: Java’s object-oriented nature and dynamic memory allocation make it difficult to predict when memory will be released.
  • Avoiding frequent pauses: Garbage collection can significantly impact application performance. By not guaranteeing a specific timing, the JVM can optimize garbage collection to minimize disruptions. (Though difference garbage collection algorithms can be selected, but even then, it’s still not technically guaranteed as to when garbage collection will run)
  • Real-time constraints: Some applications, like real-time systems, require deterministic behavior. In these cases, developers might need to implement their own memory management strategies or use specialized JVMs.

So how can you hook into garbage collection? Well, objects created in Java inherit from the Object class, which means that all objects have certain methods that we can override: finanlize() is a method that is invoked by the garbage collector prior to the object being destroyed.

This might be a good place for you to invoke some business logic that you need to perform upon the object being deleted from memory, but remember: There is no guarantee when garbage collection will run, and therefore the logic that you put in the finalize() method of your object shouldn’t really be mission critical as it may or may not run when you expect it to. Also, garbage collection is generally an expensive task and we should really let the JVM do its things since it’s already highly optimized. (There really are very few reasons why you should be doing your own garbage collection in Java, and if you’re on the fence as whether or not it’s something you explicitly need to handle, you probably don’t.)

Furthermore, handling your own garbage collection is generally frowned upon, and if you do need to handle some kind of clean up activities for when objects are no longer needed, you should probably be building this logic into a service or a method that lives on the object that is going to be destroyed, or if you’re using Spring or Spring Boot, this is probably already managed for you to an extent.