Garbage Collection in Depth: Understanding
Introduction to Garbage Collection in Java
Garbage collection (GC) is the process of automatically
identifying and reclaiming memory that is no longer in use by the program. Java
provides this capability to manage memory automatically, so programmers don’t
need to explicitly free up memory as they would in languages like java, C/C++. The
goal of the garbage collector is to find and remove objects that are unreachable
and no longer needed by the application, freeing up memory for future
allocations.
Java uses several algorithms to perform garbage collection,
like Mark-and-Sweep and Generational GC.
Ways an Object Becomes Eligible for Garbage Collection
Objects become eligible for garbage collection in several
ways:
- Nullifying
an Object Reference When an object is no longer needed, setting its
reference to null will make it eligible for garbage collection if no other
references to the object exist.
java
Student s1 =
new Student(); Student s2 =
new Student(); // Currently,
no object is eligible for GC s1 =
null; // Now the object referenced by
s1 is eligible for GC s2 =
null; // Now the object referenced by
s2 is eligible for GC |
Once s1 and s2 are set to null, the objects they reference
are no longer reachable and are eligible for garbage collection.
- Reassigning
a Reference If a reference variable that holds an object is reassigned
to another object, the previously referenced object becomes eligible for
garbage collection if no other reference exists.
java
Student s1 =
new Student(); Student s2 =
new Student(); // Currently,
no object is eligible for GC s1 = new
Student(); // The original object
referenced by s1 is now eligible for GC s2 = s1; // The original object
referenced by s2 is now eligible for GC |
Here, reassigning s1 to a new object makes the original
object referenced by s1 eligible for garbage collection. When s2 = s1 happens,
the old object that s2 was pointing to becomes eligible for garbage collection.
- Objects
Created Inside a Method Any object created inside a method is eligible
for garbage collection once the method finishes execution, provided there
are no references to it outside the method.
i. Method Holds a Reference Variable
If a method holds a reference variable that is returned, the object will remain
in memory until the returned reference is no longer in use:
java
public
Student createStudent() { Student s = new Student(); // Local object reference return s;
// As long as the returned reference exists, the object isn't GC
eligible } |
ii. Method Doesn’t Hold a Reference Variable If the
method does not return a reference or store it elsewhere, the object will be
eligible for garbage collection after the method completes:
java
public void
createStudent() { Student s = new Student(); // Local reference // No return statement or global
reference } // The object
is eligible for GC once the method execution finishes |
iii. Global Object Reference in Method If a method
assigns an object to a global variable, it won't be garbage-collected until the
global reference is reassigned or set to null:
java
Student
globalStudent; public void
createGlobalStudent() { globalStudent = new Student(); // Global reference holds the object } // The object
remains in memory as long as globalStudent references it |
Island of Isolation
An island of isolation occurs when a group of objects
reference each other but are otherwise unreachable from the root references
(such as from the stack or static fields). Even though the objects are mutually
referenced, they can still be garbage collected because no active parts of the
application can reach them.
For example:
java
class Student
{ Student friend; } Student s1 =
new Student(); Student s2 =
new Student(); s1.friend =
s2; s2.friend =
s1; s1 = null; s2 =
null; // Both s1 and s2 are now
eligible for GC despite referencing each other |
Here, s1 and s2 reference each other, but since they are no
longer reachable from outside references, they form an "island of
isolation" and are eligible for garbage collection.
Conclusion
Garbage collection in Java ensures that memory is managed
automatically, and objects no longer in use are reclaimed. There are various
ways objects become eligible for garbage collection:
- Nullifying
references.
- Reassigning
references.
- Method-scoped
objects.
- Island
of isolation where circular referencing occurs but no external access
exists.
Key Notes:
- NB
1: When an object has no reference, it is eligible for garbage collection.
- NB
2: If an object has references but these references are isolated from
external access (i.e., Island of Isolation), it is also eligible for
garbage collection.
How to Call the Garbage Collector
There are two ways to explicitly suggest that the garbage
collector runs:
Ø Using
the System class:
java
System.gc(); // Static method |
Ø Using
the Runtime class:
java
Runtime.getRuntime().gc(); // Instance method |
Difference Between System.gc() and Runtime.getRuntime().gc()
Ø
System.gc() is a static method of
the System class. It internally calls Runtime.getRuntime().gc().
ü
Performance: Slower compared to direct Runtime.gc()
call.
Ø
Runtime.getRuntime().gc() is an instance
method of the Runtime class, and it directly triggers garbage collection.
ü
Performance: Better performance since it avoids
the overhead of the System.gc() wrapper.
Finalization:
Ø The
finalize() method is called by the garbage collector before an object is
destroyed, to allow for clean-up actions (like closing file streams).
Ø Signature:
java
protected
void finalize() throws Throwable {} |
Case 1: Finalize Method Called by GC
When an object is no longer referenced and is eligible for
GC, the finalize() method (if overridden) will be called once before
destruction.
Example 1: Calling System.gc() on a String Object (No
Custom Finalization):
Java
public class
Test { public static void main(String[] args) { String t1 = new String(); t1 = null; // Object becomes eligible for GC System.gc(); // Suggest garbage collection for t1 System.out.println("End of Main
method"); } public void finalize() { System.out.println("Kartik
destroyed"); } } |
Output:
End of Main
method |
Since t1 is a String object, and the finalize() method is
not overridden for String, no custom message will be printed.
Example 2: Calling System.gc() on a Test Object (Custom
Finalization):
java
public class
Test { public static void main(String[] args) { Test t1 = new Test(); t1 = null; // Object becomes eligible for GC System.gc(); // Suggest garbage collection for t1 System.out.println("End of Main
method"); } public void finalize() { System.out.println("Kartik
destroyed"); } } |
Possible Output 1:
End of Main
method Kartik
destroyed |
Possible Output 2:
Kartik
destroyed End of Main
method |
The order of output depends on the JVM and when it schedules
garbage collection.
Case 2: Finalize Method Called Explicitly
You can explicitly call the finalize() method, but it won’t
destroy the object. The GC will still invoke finalize() before destroying the
object.
java
public class
Test { public static void main(String[] args) { Test t1 = new Test(); t1.finalize(); // Explicit call, acts like a normal method
call t1 = null; // Eligible for GC System.gc(); // GC may call finalize again System.out.println("End of Main
method"); } public void finalize() { System.out.println("Kartik
destroyed"); } } |
Output:
Kartik
destroyed End of Main
method |
In this case, finalize() is called twice: once explicitly
and once by the garbage collector.
Case 3: Finalize Called Only Once by GC
Even if an object becomes eligible for GC multiple times, finalize()
will only be called once by the garbage collector.
java
public class
Test { static Test s; public static void main(String[] args)
throws InterruptedException { Test t = new Test(); System.out.println(t.hashCode()); t = null; System.gc(); // Eligible for GC, first call to
finalize() Thread.sleep(5000);
System.out.println(s.hashCode());
// Resurrected in finalize() s = null; System.gc(); // Eligible for GC again, finalize won't be
called again Thread.sleep(10000); System.out.println("End of Main
method"); } public void finalize() { System.out.println("Kartik
destroyed"); s = this; // Resurrecting the object } } |
Output:
2346362 Kartik
destroyed 2346362 End of Main
method |
The object is resurrected in the first finalize() call,
preventing it from being collected. The second GC invocation doesn’t call finalize()
again, as it is only called once per object.
Case 4: Garbage Collection Without Explicit System.gc()
Call
Garbage collection can be triggered automatically by the JVM
when it detects low memory conditions.
java
public class
Test { static int count; public static void main(String[] args) { for (int i = 0; i < 1000000; i++)
{ Test t = new Test(); t = null; // Eligible for GC } System.out.println("End of Main
method"); } public void finalize() { count++; System.out.println("Kartik
destroyed " + count); } } |
Output: Garbage collection will be triggered
automatically by the JVM when memory is low, and each Test object will have finalize()
called at least once, printing "Kartik destroyed".
Memory Leaks in Java
Memory leaks occur when objects are no longer needed but are
not garbage-collected because they are still referenced somewhere in the
program. Tools like HP-J-Meter, IBM-Tivoli, and J-Profiler
can help detect memory leaks.
String vs. StringBuffer
Ø String:
Immutable; modifications create new objects.
java
String s =
new String("Kartik"); s.concat("Mandal"); // Creates a new String, s still points to
"Kartik" System.out.println(s); // Output: Kartik |
Ø StringBuffer:
Mutable; modifications change the existing object.
java
StringBuffer
sb = new StringBuffer("Kartik"); sb.append("Mandal"); System.out.println(sb); // Output: KartikMandal |
Conclusion
Garbage collection and memory management are automatic in
Java, but understanding how objects become eligible for GC and how to work with
finalize() can help avoid memory leaks and optimize resource management.
0 Comments