How the JVM manages memory
11th May 2020 by Aneesh Mistry

Key Takeaways
• Memory management plays a vital role in the performance of an application.
• Java uses a garbage collector to automatically manage memory on behalf of the developer.
• There are six different types of garbage collectors.

Automated memory collection in Java

Memory management is the process of assigning memory to Objects created by an application and releasing the memory when the Object is no longer required.
In languages such as C++ and C, the developer will need to manually allocate Objects memory using methods such as malloc() and calloc(). If the developer does not remember to release the Object from the memory, a Memory leak could occur.

Memory leak: when an Object continues to consume memory even after it is needed or when an Object stored in memory cannot be accessed by the programme.

Memory management can be completed with many different strategies to ensure the application efficiently frees and allocates memory during execution. Memory management techniques used within applications are primarily concerned with the performance requirements of the following factors:
Throughput: how quickly memory can be freed from Objects that are no longer required.
Stop the world events: a period when the application is completely stopped to allow memory to be freed and assigned to all live Objects in the application.
Memory fragmentation: when memory is unevenly distributed across a memory block, leaving gaps of unfilled memory slots between Objects. There is a cost-benefit consideration that may arise between the performance gain of leaving memory fragmented against the cost of memory defragmentation to the application runtime.


Garbage collectors

Java makes use of garbage collectors which promise the developer that Objects later referenced within the application will not be deleted from memory. Without such as promise, it is possible that a separate thread may obtain access to an Object reference and remove its memory allocation thus resulting in a null pointer when the Object is referenced from a different thread.


The different types of garbage collectors

There are 6 different types of garbage collectors that can be selectively used together. The different types of collectors allow an application to efficiently manage memory with the relative importance of throughput, memory fragmentation, and stop the world events implemented by design.


Do nothing garbage collector

Do nothing collectors will only guarantee that live Objects are kept alive.

Reference counting garbage collector

Reference counting resolves the problem of having circular references within our code. As the garbage collector is responsible for collecting dead (unreferenced) Objects, a circular reference, where two Objects reference each other, can create a condition whereby neither Object will be collected. Circular referencing exists in the example below:

class One {
    private Two two;
     
    public void setTwo(Two two) {
        this.two = two;
    }
}
 
class Two {
    private One one;
     
    public void setOne(One one) {
        this.one = one;
    }
}
 
public class App {
    public static void main(String[] args) {
        //one instance of each class is created
        One instanceOne = new One();
        Two instanceTwo = new Two();
 
        // A circular reference is made between the two classes
        instanceOne.setTwo(instanceTwo);
        instanceTwo.setOne(instanceOne);
 
        // Although the classes are now null, their reference to each other
        // remains in the other's method, therefore preventing collection
        instanceOne = null;
        instanceTwo = null;
    }
}

Reference counting will resolve the problem of circular references; each Object will own a counter that is incremented and decremented as they are referenced and dereferenced. As a result, the counter assigned to each Object will allow the garbage collector to identify Objects that are truly referenced by the root set.

Root set: the initial set of Objects from which the reachability of all the other Objects can be derived upon.


Mark and sweep garbage collector

Mark and sweep performs three individual phases in garbage collection:
Mark: identify Objects that are currently in use.
Sweep: remove unused Objects.
Compact: remove memory fragmentation that may occur from the sweep phase.

Mark phase

During the mark phase, the Objects that are reachable are identified by following the course of the root set.
In the image below, the identified Objects within the memory are marked as 'live', Objects that reference each other without a root set reference will not be marked as live.

Mark phase diagram


Sweep phase

During the sweep phase, the Objects that were not marked as 'live' are removed from the heap memory. As a result, the memory block contains only live Objects.

Sweep phase diagram


Compact phase

The current memory block contains a fragmented distribution of Objects. During the compact phase, the physical memory address of each Object is changed. The Objects are rearranged in the memory block so that they are aligned next to each other. If a compact phase was not executed, Objects would need to fit within the fragmented spaces of the memory block if they are to be stored. Following the compact phase, the memory block has an open space to add Objects as they are assigned to the heap, thus allowing the full memory block to be utilised.

Compact phase diagram

Copying garbage collector

The copying garbage collector uses two memory blocks: 'fromSpace' and 'toSpace'. During each garbage cycle, Objects will be stored in one of the blocks; the block used for storage is known as the 'fromSpace' block. When the 'fromSpace' block becomes full, a mark and sweep will be performed by the garbage collector, moving the live Objects into the 'toSpace' memory block. The 'fromSpace' memory block will now be empty and will be referenced as the 'toSpace' memory block for the next collection cycle.

Copying diagram


Incremental garbage collector

An incremental garbage collector does not look at all the Objects during a collection; the cost of the collection is therefore reduced. Similar to the copying garbage collector, the incremental collector will use different memory blocks to divide the Objects that are to be reviewed based upon certain characteristics.


Generational garbage collector

A generational garbage collector is a form of incremental garbage collector. Generational collectors aim to improve the efficiency of a mark and sweep cycle by dividing Objects into sets (generations) according to when they were last used. As an Object survives a mark and sweep cycle, they are promoted into an 'older' generation which are reviewed less frequently for collection. Different generational garbage collectors will implement rules that determine when an Object is promoted and when generations are reviewed by the cycle. Once an Object is moved into the older generation, the younger generation is cleared to allow new Objects to be assigned memory.


How the Java Virtual Machine uses garbage collection

The JVM uses generational, copying, and mark and sweep garbage collectors strategies. The JVM garbage collector consists of two generations: young and old. There are two types of garbage collections: minor collections that occur within the young generation, and full collections that occur across both generations.
The young generation has 3 different memory spaces within it:
Eden space: where new Objects are directly allocated memory.
Survivor space 1 and 2: live Objects are assigned into one of the spaces following a minor collection. The two spaces are used for copying garbage collection and memory defragmentation.

Garbage collection cycles within the JVM
The example below shows the movement of Objects within a JVM garbage collection:
1. New Objects are assigned to the Eden space upon creation.
2. When the Eden space is full, a minor collection is run using a mark and sweep.

Eden space full

3. Objects that survive the mark and sweep are assigned into one of the survivor spaces. Below, the marked Objects from the Eden space are moved into the Survivor 1 memory block.

Live objects into survivor 1

4. When the Eden space becomes full again, the minor collection will be performed on the Eden and Survivor 1 space. The Objects that survive the collection are moved into the survivor space 2.

Mark sweep survivor 1

The Objects are compressed in survivor space 2. They have a number assigned to them which represents the number of cycles they have survived.

Copy into survivor 2

5. The JVM can define a number of cycles an Object must survive before it is eligible to be promoted into the old generation. In the example below, 3 cycles are used as the promotion value. When the survivor and eden space are full, Objects that meet the criteria for promotion are moved into the old generation. The old generation Objects will no longer be reviewed by the minor collection.

Mark sweep survivor 2


Copy into survivor 1 and old

6. When the old generation eventually becomes full, a full garbage collection is run. The full collection will scan both generations, perform a mark and sweep and remove dead Objects from the young and old generations.

The JVM garbage collector assumes that Objects either live for a short time, or they live forever. By dividing the memory blocks into generations, the memory management system can utilise minor collections to identify and remove short-lived Objects while only using stop the world events sparingly during full collections of the old and young generation.


Conclusion

Garbage collectors introduce different types of mechanisms for allocating and freeing memory within an application. There are many variables that are involved with garbage collection that require consideration. The many types of implementations also allow the garbage collector to leverage different styles to efficiently manage memory. The JVM garbage collector process covered in this blog post is the default serial garbage collector. The multi-threaded capability of Java can enable concurrent garbage collectors to minimise the use of stop the world events. There are three other collectors that can be used in Java which offer various advantages and drawbacks for application behaviour: G1, parallel, and CMS.


Share this post