Unpredictable! Dangerous! Unnecessary! The author Joshua Bloch makes sure to set the tone of the item right in the beginning by using these adjectives. To augment this view point on finalizers he says that it can be used as a rule of thumb to avoid them altogether. That is a pretty strong point of view.
Just to give you some background the finalize() method in Java is a special method of the class Object. It will be invoked by the garbage collector (GC) on an object when GC determines that there are no more references to the object i.e. just before GC reclaims the object. It sounds something similar to the destructor in C++, right? So, can it be used to cleanup any external resources like closing files?
Wrong!
The author gives the following 3 reasons to support his point of view:
- The promptness of the finalize methods' execution is not guaranteed.
- There is a severe performance penalty for using finalizers.
- Uncaught exceptions thrown by finalize method is ignored, and finalization of that object terminates.
The promptness of the finalize methods' execution is not guaranteed
The author says that there is no fixed time interval between the time an object becomes eligible for garbage collection and the time its finalizer is executed. It is dependant on the garbage collection algorithm which varies according to the JVM implementation. Further he adds that there is no guarantee that the finalize method will ever get executed at all. So now you can imagine how dangerous it is to depend on finalizers.
I was curious to find out because I have never actually made use of the finalize method in any of my code. So I wrote a small program to test it.
Here I have one class which had a FileInputStream object and a method which tries to open a file. I have overridden the finalize method where I close the file. I have added one print statement in each block (try-catch-finally-finalize) to better understand the flow of execution. I have also created another class which will create an instance of this class, invoke the openFile method and further nullify the reference to make the instance eligible for garbage collection.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | import java.io.FileInputStream; import java.io.FileNotFoundException; public class OpenFileTest { private FileInputStream file = null; public void openFile(String filename) { try { System.out.println("Inside try block"); file = new FileInputStream(filename); } catch (FileNotFoundException e) { System.out.println("Inside catch block"); System.err.println("Could not open file "+filename); } finally { System.out.println("Inside finally block"); } } @Override protected void finalize() throws Throwable { System.out.println("Inside finalize method"); if(file!=null){ file.close(); file = null; } super.finalize(); } } |
1 2 3 4 5 6 7 8 9 | public class TestFinalizeMain { public static void main(String[] args) { OpenFileTest o = new OpenFileTest(); o.openFile("C:\\shreyas\\CusumCalc\\cusum_output.csv"); o = null; } } |
Output:
Inside try block Inside finally block
Wait, that is not what was supposed to happen. What happened to the finalize method being called by our garbage collector?
So, this is exactly what the author was talking about.
Here we can see that the finally block did get executed without any issues. So using finally block to deallocate resources is a safe bet.
There are other ways of forceful execution of the finalize method like System.gc(), but then again it just increases the odds and does not provide any guarantee.
There is a severe performance penalty for using finalizers.
The author says that the time to create and destroy an object on his machine apparently increased by 430 times when he used finalizer. Well, if you come to think of it this is actually true. The work of the garbage collector is to scan the heap often and determine which objects are no longer referenced and de-allocate the memory. But if an object uses a finalizer then the garbage collector is interrupted. And it so happens that finalizers are processed on a thread that is given a fixed, low priority. So objects that are otherwise eligible for garbage collection will be pending on finalization and use up all the available memory causing your application to slow down.
Uncaught exceptions thrown by finalize method is ignored
Now here is another good reason why you are better off without a finalizer. Any uncaught exception thrown during a finalization is ignored and it will not be propagated further and the finalization halts. Java handles uncaught exceptions by terminating the thread and usually printing the stack trace to the console. But in this case there will be no warning by means of any message being printed.
So, what is a good alternative if you want to do all your resource releasing work then? The author suggests providing an explicit termination method and requiring the clients of the class to invoke this method on each instance when it is no longer needed would be your best bet. Some good examples of such methods are the close methods on InputStream, OutputStream, and java.sql.Connection.
Another option I could think of is to use the try-with-resources statement provided starting from Java 7.
Now all that said and done the question remains as what is a real good use of the finalize method?
In some rare cases if the user forgets to call the explicit termination method of an object then a finalizer can be used as an extra level of safety to free the resource, better late than never. But the author says that it is better to use safety and precaution if you must use it and one way he suggests is to add the finalization code in a try block when you override the finalize method and invoke the super class finalizer (super.finalize()) in the finally block. This is because "finalizer chaining" is not performed automatically.
A final thought - You are better off without a finalizer!
No comments:
Post a Comment