...
Wiki Markup It is not advisable to use any lock or sharing based mechanisms within a finalizer due to the inherent dangers of deadlock and starvation. On the other hand, it is also easy to miss that there can be synchronization issues with the use of finalizers even if the source program is single-threaded. This is because the {{finalize()}} methods are called from their own threads. If a finalizer is inevitable, the cleanup data structure should be protected from concurrent access (See \[[Boehm 05|AA. Java References#Boehm 05]\]).
Noncompliant Code Example
The System.runFinalizersOnExit()
method has been used in this noncompliant example to simulate a garbage collection run (note that this method is deprecated due to thread-safety issues).
Wiki Markup |
---|
According to \[[API 06|AA. Java References#API 06]\] class {{System}}, {{runFinalizersOnExit()}} documentation: |
Enable or disable finalization on exit; doing so specifies that the finalizers of all objects that have finalizers that have not yet been automatically invoked are to be run before the Java runtime exits. By default, finalization on exit is disabled.
The SubClass
overrides the protected finalize
method and performs cleanup. Subsequently, it calls super.finalize()
to make sure its superclass is also finalized. The unsuspecting BaseClass
calls the doLogic()
method which happens to be overridden in the SubClass
. This resurrects a reference to SubClass
such that it is not only prevented from getting garbage collected but also cannot use its finalizer anymore in order to close new resources that may have been allocated by the called method.
Code Block | ||
---|---|---|
| ||
class BaseClass {
protected void finalize() throws Throwable {
System.out.println("Superclass finalize!");
doLogic();
}
public void doLogic() throws Throwable{
System.out.println("This is super-class!");
}
}
class SubClass extends BaseClass {
protected void finalize() throws Throwable {
System.out.println("Subclass finalize!");
try {
// cleanup resources
} finally {
super.finalize(); // call BaseClass' finalizer
}
}
public void doLogic() throws Throwable{
System.out.println("This is sub-class!");
// any resource allocations made here will persist
}
}
public class BadUse {
public static void main(String[] args) {
try {
BaseClass bc = new SubClass();
System.runFinalizersOnExit(true); // artificially simulate finalization (do not do this)
} catch (Throwable t) { /* handle error */ }
}
}
|
A expected, this code outputs:
Code Block |
---|
Subclass finalize!
Superclass finalize!
This is sub-class!
|
Compliant Solution
This solution eliminates the call to the overridable doLogic()
method from within the finalize()
method.
Code Block | ||
---|---|---|
| ||
class BaseClass {
protected void finalize() throws Throwable {
System.out.println("superclass finalize!");
// eliminate the call to the overridden doLogic().
}
...
}
|
Exceptions
OBJ02-EX1: Sometimes it is necessary to use finalizers especially while working with native objects/code. This is because the garbage collector cannot re-claim memory from code written in another language. Also, the lifetime of the objects is often unknown. Again, the native process must not perform any critical jobs that require immediate resource deallocation.
...