Learning about Memory Management in Java and Go
"TLDR: This article introduces the study of memory management in Java and Go, using GC to achieve automatic memory management, which helps in gaining a deeper understanding of performance bottlenecks. GC is divided into three types: Serial GC, Parallel GC, and Concurrent GC, each with its own characteristics and applicable scenarios. GC strategies include Copying GC, Mark-sweep GC, and Mark-compact GC, each with its own advantages and disadvantages. Reference counting is one method for determining dead objects, but it has drawbacks such as thread safety issues and the inability to handle cyclic structures. Java memory management technology has evolved over multiple generations, adopting generational collection, which selects different cleanup strategies based on the lifespan of objects. Golang uses the tri-color marking method to track dead objects, using white, gray, and black to represent objects that can be reclaimed, are being examined, and cannot be reclaimed, respectively."
Java and Go both achieve automatic memory management through GC, improving coding efficiency and safety. By learning about automatic memory management, we can gain a deeper understanding of the performance bottlenecks of Java and Go in certain scenarios.
GC-Related Concepts

As shown in the figure, business threads allocate objects, while GC threads reclaim unused objects.
Depending on the scenario, GC is divided into three types:
- Serial GC: Single-threaded GC, which causes pauses
- Parallel GC: Multi-threaded GC, which still causes pauses
- Concurrent GC: Hybrid concurrent GC, which does not cause pauses
GC Strategies
To improve memory utilization, GC compacts memory so that surviving objects are arranged contiguously, and free memory forms a single large block. This compaction can:
- Improve memory allocation efficiency.
- Avoid repeated attempts when allocating large objects.
- Reduce performance issues caused by fragmentation.
When an object is confirmed as garbage, GC also has several different sweeping strategies:
-
Copying GC: Copies surviving objects to another memory space (additional space)

The original storage space of surviving objects can then be used to allocate other objects. When there are only a few surviving objects (for example, only a small number of objects survive in the young generation), moving them to the additional space is highly efficient, and the remaining original space becomes a large, fully allocatable block of memory. When there are many surviving objects (such as long-lived objects in the old generation), moving a large number of objects to the new space becomes time-consuming.
-
Mark-Sweep GC: Marks the memory of dead objects as allocatable

The storage space of dead objects is stored in a free list linked list, and when an object needs to be allocated next time, space is found from the free list. However, the disadvantage of the Mark-Sweep algorithm is that it only marks and clears objects without compacting memory, so the problem of memory fragmentation still exists, and it may be impossible to allocate memory for large objects.
-
Mark-Compact GC: Moves and compacts surviving objects

Surviving objects are compacted in place and moved to the very beginning, that is, surviving objects are compacted and compressed (no additional memory is used, no extra space is needed, which is the biggest difference from Copying GC). Then the large space after the black blocks can continue to be used to allocate other objects. This is the method used for moving objects in the old generation. The pause caused by moving is unavoidable, but it avoids occupying additional memory space.
Reference Counting
We know the three different GC strategies for dead objects, but we have not yet learned how to determine whether an object is dead. A common method is reference counting. When an object has no references pointing to it, it means it has been abandoned and can be determined as a dead object. Of course, reference counting is not a panacea and has many disadvantages.
-
Advantages:
- Simple and convenient: From a programming perspective, reference counting is naturally transparent to programmers, making development more convenient and reducing mental burden.
-
Disadvantages:
-
Reference counting itself relies on atomic operations to achieve atomicity and visibility, which inherently reduces performance. In concurrent environments, multiple threads may operate on the same object, and thread safety issues must be handled carefully.
-
It cannot handle circular structures, that is, circular references.

For example, the red objects in the figure form a circular structure. The objects within the cycle are unreachable and should be completely reclaimed, but this is difficult to achieve with reference counting.
Of course, different languages have coping strategies, such as using
weak_ptrin C++ to solve this problem. -
Memory overhead. Each object needs to maintain an additional storage space to store the reference count.
-
Java Memory Management Technology
Java's memory management technology has evolved over multiple generations and is relatively mature.
Generational Collection
Based on the assumption that "most objects have a very short lifespan, while a small number of objects are long-lived, and different collection strategies should be adopted." For example, objects created within a function are immediately abandoned after the function finishes executing, since the function execution time is very short — these are short-lived objects.
-
Young generation GC strategy
- The number of surviving objects is very small, so
Copying GCis suitable. All surviving objects are moved to a fixed area.
- The number of surviving objects is very small, so
-
Old generation GC strategy
- Objects tend to remain alive for a long time, and repeatedly copying them incurs significant overhead, making
Mark-Sweep GCmore suitable. Dead objects are linked together through a free list, and when an object needs to be allocated next time, space is taken from the free list.
- Objects tend to remain alive for a long time, and repeatedly copying them incurs significant overhead, making
Golang Memory Management Technology
Tri-Color Marking
Golang uses the tri-color marking method to track dead objects. The specific process is as follows:
- Assume all objects are unused and reclaimable, and mark them all as white.
- Find an object that is currently being referenced, trace the other objects it references, mark them as gray, and then change itself to black.
- Black represents objects that have been checked and cannot be reclaimed, while white represents objects that can be reclaimed. All white objects are then cleared.