Garbage Collection (GC) in Java is an automatic memory
management process that reclaims memory by removing objects that are no longer
in use, preventing memory leaks. Java's GC process is designed to track the
life cycle of objects and ensure that unused objects are removed, thus freeing
up memory for new objects.
Key Concepts of Java Garbage Collection
- Heap
Memory:
Ø
The heap is where all the objects are allocated
memory dynamically. It's divided into two main parts:
ü
Young Generation: Where newly created
objects are placed.
ü
Old Generation: Where long-lived objects
are stored.
- Garbage
Collection Roots (GC Roots):
Ø
GC roots are special objects used as starting
points for identifying live objects. Common GC roots include:
ü
Active Threads
ü
Static variables
ü
Local variables in method calls
ü
JNI references
- Reachability:
Ø
An object is considered "reachable" if
it can be accessed through a chain of references from the GC roots. If an
object cannot be reached, it is considered eligible for garbage collection.
- Mark
and Sweep Algorithm:
Ø
Java's GC uses this algorithm to determine which
objects are no longer used:
ü
Mark Phase: The GC marks all reachable
objects.
ü
Sweep Phase: All unmarked objects
(unreachable) are removed, and their memory is reclaimed.
Types of Garbage Collectors in Java
Java provides different types of garbage collectors with
varying performance characteristics:
- Serial
Garbage Collector:
Ø
A simple and basic GC that works in a single
thread, pausing all application threads during garbage collection. It is
suitable for small applications and is the default collector for
single-threaded environments.
- Parallel
Garbage Collector (Throughput Collector):
Ø
This collector uses multiple threads for both
the young and old generation collections, minimizing GC pauses by performing
the work in parallel. It is suited for applications requiring high throughput.
- CMS
(Concurrent Mark-Sweep) Garbage Collector:
Ø
The CMS collector is designed to minimize
application pauses by running the GC concurrently with the application. It uses
multiple threads to perform garbage collection in the old generation and is
suitable for applications requiring low-latency.
- G1
(Garbage First) Garbage Collector:
Ø
G1 is designed for larger heap sizes and
performs collection in both the young and old generations concurrently. It
divides the heap into regions and performs garbage collection based on regions
with the most garbage. It is well-suited for multi-processor machines and
applications that require predictable pauses.
- Z
Garbage Collector (ZGC):
Ø
A low-latency GC designed for applications that
need minimal GC pauses, even with large heaps. It performs most of the work
concurrently with application execution.
- Shenandoah
Garbage Collector:
Ø
Shenandoah is another low-latency garbage
collector focused on reducing pause times by running concurrent phases for
almost all GC steps.
Garbage Collection Algorithms
- Serial
Garbage Collector:
Ø
Algorithm: Single-threaded and designed
for small applications.
Ø
Process: All threads are stopped during
garbage collection (Stop-the-World event). It marks, sweeps, and compacts
memory using a single thread.
- Parallel
Garbage Collector:
Ø
Algorithm: Multi-threaded version of the
serial collector.
Ø
Process: Multiple threads are used for
the marking and sweeping phase, improving throughput but still leading to
stop-the-world pauses.
- Concurrent
Mark-Sweep (CMS) Collector:
Ø
Algorithm: Concurrent collector for the
old generation.
Ø
Process: It marks objects concurrently
with application threads, minimizing pauses but can lead to heap fragmentation.
- Garbage
First (G1) Garbage Collector:
Ø
Algorithm: Region-based collection.
Ø
Process: Divides the heap into regions,
collecting those with the most garbage first. It can be concurrent and provides
predictable pause times.
Ø
Compaction: After marking and sweeping,
G1 may also compact memory to reduce fragmentation.
- Z
Garbage Collector (ZGC):
Ø
Algorithm: Low-latency garbage collector.
Ø
Process: It performs most of its work
concurrently with application threads, ensuring very short pauses, even with
very large heap sizes.
mark and sweep algo |
Detailed Flow of GC:
- New
Object Creation:
Ø
Objects are created in the Eden space of
the young generation.
- Minor
GC:
Ø
When Eden fills up, a Minor GC occurs.
Reachable objects are moved to a survivor space, while unreachable
objects are discarded.
- Promotion
to Old Generation:
Ø
Objects that survive several Minor GCs are
eventually promoted to the old generation.
- Major
GC:
Ø
When the old generation fills up, a Major GC
is triggered, which is more expensive but clears the old generation of
unreachable objects.
Generations in Java Heap
- Young
Generation:
Ø
This is where all new objects are allocated. It
is divided into three areas:
ü
Eden Space: Where new objects are
created.
ü
Survivor Space: Holds objects that
survive one or more garbage collection cycles in Eden.
Ø
Minor GC: When Eden fills up, a minor GC
occurs, moving surviving objects to the survivor spaces or promoting them to
the old generation.
- Old
Generation:
Ø
Objects that have lived long enough are promoted
here. When this part of the heap becomes full, a Major GC (or Full GC)
occurs, which is more expensive and can result in longer pause times.
Phases of GC in Java
- Minor
Garbage Collection:
Ø
This collects garbage from the young generation.
It is generally fast but happens frequently since the young generation fills up
quickly.
- Major
(or Full) Garbage Collection:
Ø
This collects garbage from both the young and
old generations. It is a more expensive operation in terms of time because it
involves more memory and often causes a stop-the-world event where all
application threads are paused.
Important JVM Flags for Garbage Collection
Ø -XX:+UseSerialGC:
Enables the serial garbage collector.
Ø -XX:+UseParallelGC:
Enables the parallel garbage collector.
Ø -XX:+UseConcMarkSweepGC:
Enables the CMS garbage collector.
Ø -XX:+UseG1GC:
Enables the G1 garbage collector (default from Java 9).
Ø -Xms:
Sets the initial heap size.
Ø -Xmx:
Sets the maximum heap size.
Benefits of Garbage Collection in Java
Ø Automatic
Memory Management: Java developers do not need to manually manage memory
allocation and deallocation.
Ø Reduces
Memory Leaks: Objects no longer in use are automatically removed.
Ø Improved
Application Stability: By reclaiming memory, GC ensures that an application
doesn’t run out of memory, leading to more stable execution.
Downsides of Garbage Collection
Ø Performance
Impact: Garbage collection can introduce pauses (stop-the-world events)
where the application is paused while memory is being reclaimed.
Ø Overhead:
In large applications, the overhead of garbage collection can become
significant, affecting application performance.
How Garbage Collection Works in Java with a Class Example
Java automatically manages memory through Garbage
Collection (GC), which is designed to find and remove objects that are no
longer used by the program, freeing up heap space. Java uses the Mark and
Sweep algorithm and several advanced techniques like Generational
Garbage Collection to manage object lifecycle effectively.
Here's a detailed explanation of garbage collection in Java
with a code example and an explanation of how the algorithm works.
Example Code: Garbage Collection in Action
java
class
GarbageCollectionExample { private String objectName; public GarbageCollectionExample(String
name) { this.objectName = name; } @Override protected void finalize() throws
Throwable { // Called when the object is garbage
collected System.out.println(objectName +
" is garbage collected."); } public static void main(String[] args) { // Creating objects GarbageCollectionExample obj1 = new
GarbageCollectionExample("Object 1"); GarbageCollectionExample obj2 = new
GarbageCollectionExample("Object 2"); GarbageCollectionExample obj3 = new
GarbageCollectionExample("Object 3"); // Dereference objects to make them
eligible for GC obj1 = null; obj2 = null; // Suggest garbage collection System.gc(); // Requesting garbage
collection // More operations obj3 = null; // Another GC request Runtime.getRuntime().gc(); // Another
way to request GC } } |
Key Parts of the Example:
- Dereferencing:
Ø
Objects obj1 and obj2 are set to null, making
them eligible for garbage collection.
Ø
System.gc() and Runtime.getRuntime().gc() hint
the JVM to start garbage collection, but it’s not guaranteed when it will
happen.
- finalize()
Method:
Ø
The finalize() method is called by the garbage
collector just before an object is removed from memory. It’s a good place to
release non-memory resources (like files, sockets), although it's rarely used
in modern applications.
- Output:
Ø
When the garbage collector runs, it invokes finalize()
on obj1 and obj2, and you’ll see output like:
Plain text
Object 1 is
garbage collected. Object 2 is
garbage collected. |
Mark and Sweep Algorithm for Garbage Collection
The Mark and Sweep algorithm is the foundation of how
Java garbage collection works. Here's how it operates:
- Mark
Phase:
Ø
The GC starts at the root objects (called GC
Roots) and traverses all reachable objects, marking them as
"alive."
Ø
GC Roots include:
ü
Active threads
ü
Local variables in stack frames
ü
Static fields
ü
JNI references
Ø
Any object that can be reached from a GC Root is
considered "in use" and is marked for retention.
- Sweep
Phase:
Ø
After the mark phase, the GC sweeps through the
heap and collects any objects that were not marked as reachable. These unmarked
objects are considered garbage and are cleared from memory.
- Compaction
(optional):
Ø
Some garbage collectors (like the G1 GC) may
compact memory after the sweep phase to reduce fragmentation by moving
surviving objects together in memory.
Diagram: Mark and Sweep Process
Diff
Heap Memory +----------------+------------------------+ | Objects | | | | | | Reachable
| Unreachable | | Objects |
Objects | +----------------+-------------------------+ ^ | Mark phase: GC traverses the heap, marks
reachable objects. +---------------------------+ | Sweep
Phase: | | Collects
the | | unreachable
objects| | Clears
memory | +---------------------------+ |
Generational Garbage Collection
Java's garbage collectors use a generational approach
to optimize the performance of garbage collection. The heap is divided into:
- Young
Generation:
Ø
Most newly created objects are located here. It
is further divided into:
ü
Eden Space: Where new objects are
created.
ü
Survivor Spaces (S0 and S1): Objects that
survive one garbage collection cycle are moved here.
Ø
Minor GC: A garbage collection that
happens only in the young generation, quickly freeing space for new objects.
- Old
Generation:
Ø
Objects that have survived multiple GC cycles
are moved here. They are considered long-lived objects.
Ø
Major GC / Full GC: A garbage collection
event that involves both the young and old generations. It's more expensive in
terms of time.
Monitor of JVM memory usages
To monitor the JVM's memory usage and garbage collection
activities using the jstat command, here’s a step-by-step guide based on your
setup:
Steps:
- Start
Your Application: You have already started the Java application with
the following command:
Bash
java -Xmx120m
-Xms30m -Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseSerialGC -jar
Test.jar |
Ø
Flags used:
ü
-Xmx120m: Maximum heap size is 120 MB.
ü
-Xms30m: Initial heap size is 30 MB.
ü
-Xmn10m: Young generation size is 10 MB.
ü
-XX:PermSize=20m: Initial permanent generation
(PermGen) size is 20 MB.
ü
-XX:MaxPermSize=20m: Maximum PermGen size is 20
MB.
ü
-XX:+UseSerialGC: Use Serial Garbage Collector.
- Find
the Process ID: To monitor the Java process, first, you need to find
its process ID (PID) by running:
bash
ps -eaf |
grep Test.jar |
From the output:
bash
501 9582 11579
0 9:48PM ttys000 0:21.66 /usr/bin/java -Xmx120m -Xms30m
-Xmn10m -XX:PermSize=20m -XX:MaxPermSize=20m -XX:+UseG1GC -jar Test.jar |
The PID of your application is 9582.
- Run
jstat to Monitor GC: Now, use the jstat command to monitor the garbage
collection statistics. The following command provides GC statistics every
second (1000 milliseconds):
bash
jstat -gc
9582 1000 |
This will print garbage collection statistics at intervals
of 1 second. The output will look like:
bash
S0C S1C
S0U S1U EC
EU OC OU
PC PU YGC
YGCT FGC FGCT
GCT 8192.0
8192.0 0.0 0.0
32768.0 16384.0 49152.0
24576.0 20480.0 10240.0 5
0.03 2 0.10
0.13 8192.0
8192.0 0.0 0.0
32768.0 8192.0 49152.0
24576.0 20480.0 10240.0 6
0.04 3 0.12
0.16 |
Column Explanations:
Ø
S0C and S1C: Current size of
Survivor 0 and Survivor 1 spaces (in KB).
Ø
S0U and S1U: Current usage of
Survivor 0 and Survivor 1 spaces (in KB).
Ø
EC: Current size of the Eden space (in
KB).
Ø
EU: Current usage of the Eden space (in
KB).
Ø
OC: Current size of the Old generation
(in KB).
Ø
OU: Current usage of the Old generation
(in KB).
Ø
PC: Current size of the PermGen (in KB).
Ø
PU: Current usage of the PermGen (in KB).
Ø
YGC: Number of young generation GC events
(Minor GCs).
Ø
YGCT: Time spent in young generation GC
(Minor GCs), in seconds.
Ø
FGC: Number of full GC events.
Ø
FGCT: Time spent in full GC, in seconds.
Ø
GCT: Total time spent in GC (Minor +
Full), in seconds.
- Stop
Monitoring: When you're done monitoring, press Ctrl + C to stop the jstat
output.
By using this method, you can continuously monitor how the
JVM performs garbage collection and see how memory is being allocated and
cleaned up, particularly in the Young Generation and Old Generation.
Explanation of the Command:
Ø jstat
-gc: This option reports the garbage collection statistics.
Ø 9582:
The process ID of your Java application.
Ø 1000:
The interval at which to report the statistics, in milliseconds (1 second in
this case).
The memory usage at the time of the error
java
package
com.kartik.error; import
java.lang.management.ManagementFactory; import
java.lang.management.MemoryMXBean; import
java.lang.management.MemoryUsage; /** * * @author Kartik Chandra Mandal * How to handle run time exception in Java
file */ public class
RunTimeMemoryException { // Constant to convert bytes to megabytes private static final int MEGABYTE = (1024
* 1024); // Method that simulates an out of memory
error public static void runOutOfMemoryError()
throws Exception { // MemoryMXBean allows you to monitor
the memory usage MemoryMXBean memoryBean =
ManagementFactory.getMemoryMXBean(); try { // Attempt to allocate a large
amount of memory long[] data = new
long[1000000000]; } catch (Exception e) { // Handle any general exceptions throw new
Exception("Exception occurred: " + e.getMessage()); } catch (OutOfMemoryError e) { // Catch OutOfMemoryError
specifically and provide heap memory details MemoryUsage heapUsage =
memoryBean.getHeapMemoryUsage(); long maxMemory =
heapUsage.getMax() / MEGABYTE; long usedMemory =
heapUsage.getUsed() / MEGABYTE; System.out.println("Memory
Usage: " + usedMemory + " MB / " + maxMemory + "
MB"); // Throw a new exception to
notify that the error was caught throw new Exception("Out of
memory error by Kartik: " + e.getMessage()); } } // Main method to run the example public static void main(String[] args) { try { // Attempt to run the
memory-consuming method runOutOfMemoryError(); } catch (Exception e) { // Print the exception message
and stack trace
System.out.println(e.getMessage()); e.printStackTrace(); } } } |
Breakdown of the Code:
- Memory
Usage Monitoring:
Ø
The MemoryMXBean is used to access the JVM's
memory management interface, which can retrieve memory statistics like heap
usage.
Ø
The MemoryUsage class provides details about the
heap's memory state (used, max, etc.).
- Exception
Handling:
Ø
First, we handle any general exceptions
with a catch (Exception e) block.
Ø
Then, we handle OutOfMemoryError
separately, since it's not a subclass of Exception. In this case, we retrieve
the current heap memory usage and rethrow it as a more general Exception with a
custom message.
- Simulating
Out of Memory:
The code attempts to allocate a large array (long[] data = new long[1000000000];). This is likely to trigger an OutOfMemoryError, particularly if your JVM is running with a limited heap size.
Running and Monitoring the Program:
- Command
to Start the Program: You can use the following JVM options to set the
heap size and simulate an out-of-memory error:
bash
java -Xmx20m
-Xms20m -XX:+UseSerialGC -jar RunTimeMemoryException.jar |
This command will set both the initial and maximum heap size
to 20 MB, which should be small enough to trigger an OutOfMemoryError when
trying to allocate the large array.
- Using
jstat: To monitor the garbage collection and memory usage, you can use
the jstat tool as follows:
bash
jstat -gc
<pid> 1000 |
Replace <pid> with the process ID of the running Java
application. This will display the memory and GC statistics every second.
Sample Output:
Upon running the program, when the OutOfMemoryError is
caught, the console will print:
bash
Memory Usage:
18 MB / 20 MB Out of memory
error by Kartik: Java heap space java.lang.Exception:
Out of memory error by Kartik: Java heap space at
com.kartik.error.RunTimeMemoryException.runOutOfMemoryError(RunTimeMemoryException.java:24) at
com.kartik.error.RunTimeMemoryException.main(RunTimeMemoryException.java:38) |
By choosing the right garbage collector and tuning JVM
parameters, developers can optimize performance based on the application's
specific needs.
Fig 1 |
1 Comments