Java Finalizer and Cleaner
- Staff Curator
- Java
- 15 Feb, 2022
- 15 Feb, 2022
- 5 min read
This essay articulates the history of finalization, its cons that lead to its deprecation. Its alternatives - try-with-resource and Cleaner APIs.
History Method finalize()
Method Object.finalize() was introduced in Java 1.0 (JDK 1.0). Garbage collection was a new feature that time. Garbage collector would allocate objects and deallocate them when you are done using them. The program can do plenty of things apart from just using the heap memory, such as file handling, network usage and graphic usage. This kind of non-heap resources managed by the operating system (OS). These needs special permissions and OS keep tracking them. Therefore, you need to let the OS know when you want to use them and when done using them.
Garbage collector isn’t aware of these external resources. Therefore, classes related to external resources such as FileInputStream have close() method on them. You have to call method close once you are done using them. However, if you don’t call resource’s close() method after using them, resources become untraceable. These objects keep using the hardware resources because the system thinks program still using them.
Finalization is way for garbage collector to involve and making sure that these resources get closed. So, basically you override method finalize() and add the code that performs the resource closure by letting the OS know.
Problem of finalize()
With time, it was realized that finalization has some problems. These important problems are:
-
Unpredictable latency in execution of the method
finalize(): Garbage collector’s main responsibility is managing the heap memory and its least priority is running thefinalize(). Even specs states that there is no guarantee thatfinalize()would run at all. Therefore, closing external resources and making sure that these can be re-used goes for the toss. As it might happen that resources are not closed at all and you may get resource exhaustion. -
Security Problem: Overiding
finalize()can execute any piece of code. It means it can re-live the dead object, which is supposed to be garbage collected. -
Method
finalize()is always enabled: As soon as the object is allocated, it is eligible to finalize. There is no way to stop its execution. It means even though you have done the proper cleanup job, the garbage collector would still do extra work, it tracks it and run the finalization when an object becomes unreachable. -
Threading Thread or threads which run finalization is not specified. This may lead to deadlock, or a single thread (for finalization execution) may inherently become multi-threaded. Thread/s that executes the finalize() are shared across all the daemon instance. It means if there is any deadlock, take a long time to finalize, then it would prevent other finalizers to run.
-
Performance The extra work of tracking and finalization execution, and memory usage by the garbage collector impacts the performance.
-
Implementation is not easy as it seems: A class overriding
finalize()needs to call its super classfinalize()method to work properly. Finalization can also throw the exception that just ignore. These two cases lead to memory and resource leak.
try-with-resource
try-with-resource introduced in Java 7. try-with-resource gives you the safest way to close your resources even if an exception is thrown. Resource classes that implement the AutoClosable interface used with try-with-resource block. This block is called the close() method when resource usages is done. It is easy to use and makes code more readable.
Cleaner class
Cleaner class is introduced in Java 9. It also performs the cleanup function like finalization. However, it doesn’t have any of issues and cons there in finalize(). It doesn’t have the threading issue like finalize(), as Cleaner creates a own separate thread that won’t be used by other cleaner threads. Or, you can control the threading by passing the thread factory to create a own thread.
How to use Cleaner
You basically have to register the object with a Cleaner. This object should implement the Runnable interface and clean up implementation should be written in run(). So when your object becomes unreachable, Cleaner will executes the runnable method.
It is recommended to use the single Cleaner instance, in a library because it has its own thread and native threads are limited. However, there are no restrictions on using the multiple cleaners.
In following code, CleaningExample is used in a try-with-resources block, therefore the close method calls the cleaning action. If the close method is not called, then cleaning action is called by the Cleaner when the CleaningExample instance has become phantom reachable.
An object is phantomly reachable when the garbage collector finds no strong, soft, or weak references, but at least one path to the object with a phantom reference. Phantomly reachable objects are objects that have been finalized, but not reclaimed.
import java.lang.ref.Cleaner;
public class Scratch {
public static void main(String[] args) {
try(CleaningExample ce = new CleaningExample();) {
System.out.println("Testing Cleaner...");
}
}
}
class CleaningExample implements AutoCloseable {
// A cleaner, preferably one shared within a library. Cleaner.create() returns a new Cleaner instance
private static final Cleaner cleaner = Cleaner.create();
private final Cleaner.Cleanable cleanable;
private final State state;
public CleaningExample() {
this.state = new State("state");
this.cleanable = cleaner.register(this, state);
}
static class State implements Runnable {
// String state is added for demo
String str;
State(String str) {
// initialize State needed for cleaning action.
this.str = str;
}
public void run() {
// cleanup action accessing State, executed at most once
System.out.println("Cleaning State = " + this.str);
}
}
public void close() {
cleanable.clean();
}
}
Difference between Cleaner and try-wtih-resource
try-with-resource is limited to close the resources opened its try block scope, whereas Cleaner APIs could be used to close the resources used during the lifecycle of the object or required by more than a single try block.