📜 ⬆️ ⬇️

finalize and finalizer

Today, let's experiment a bit with the finalize () method and destroying objects. Although even novice Java programmers have some idea that finalize () is called when the garbage collector decides to destroy your object, some things may still be unexpected. For example, let's ask ourselves: what happens to your application if the finalize () method works for a very long time?

The official finalize () documentation states the following:
There is no more references to the object.
From this, it can be assumed that the “stuck” finalize () will hang up the garbage collector stream, and the build will stop. In fact (at least in HotSpot 1.6), the garbage collector does not call finalize () methods directly, but only adds the corresponding objects to a special list by calling the static java.lang.ref.Finalizer.register (Object) method. The object of the Finalizer class is a reference to the object for which you need to call finalize (), and stores references to the next and previous Finalizer, creating a doubly linked list.

Directly the finalize () call occurs in a separate “Finalizer” thread (java.lang.ref.Finalizer.FinalizerThread), which is created when the virtual machine starts (more precisely in the static section when the Finalizer class is loaded). The finalize () methods are called sequentially in the order in which the garbage collector added them to the list. Accordingly, if some kind of finalize () hangs, it will hang up the “Finalizer” stream, but not the garbage collector. This means, in particular, that objects that do not have a finalize () method will be permanently deleted, but those that have been added will be added to the queue until the “Finalizer” hangs down, the application ends, or the memory runs out.

We illustrate this with an example. Create a class whose objects eat decent places in the heap:
static class BigObject { char[] tmp = new char[10000]; } 

And write about this main method:
  public static void main(String... args) { int i=0; while(true) { new BigObject(); try { Thread.sleep(10); } catch( InterruptedException e ) {} if(i++%100==0) System.out.println("Total: "+Runtime.getRuntime().totalMemory()+ "; free: "+Runtime.getRuntime().freeMemory()); } } 
Create an object at each iteration, and once every hundred iterations we display information about the remaining memory. Let's limit the memory of the Java-machine so as not to delay the tests, and see the result:
$ java -Xms16m -Xmx16m Test
Total: 16252928; free: 15965064
Total: 16252928; free: 14013136
Total: 16252928; free: 12011536
Total: 16252928; free: 14309664
Total: 16252928; free: 12308064
Total: 16252928; free: 14797440
Total: 16252928; free: 12795840
Total: 16252928; free: 15307784
...


Memory is properly allocated and released.
')
Now create a class whose objects perform finalize () for a very long time:
  static class LongFinalize { protected void finalize() throws Throwable { System.out.println("LongFinalize finalizer"); Thread.sleep(10000000); } } 
Add new LongFinalize () to main () before the loop. The result will be:
$ java -Xms16m -Xmx16m Test
Total: 16252928; free: 15965064
Total: 16252928; free: 14003496
Total: 16252928; free: 12001896
LongFinalize finalizer
Total: 16252928; free: 14290408
Total: 16252928; free: 12288808
Total: 16252928; free: 14777432
Total: 16252928; free: 12775832
Total: 16252928; free: 15286960
Total: 16252928; free: 13280880


As you can see, despite calling LongFinalize.finalize (), the garbage collector continues to work. Now let's add our own finalize () method to the BigObject object, which does something minor:
  static class BigObject { char[] tmp = new char[10000]; protected void finalize() throws Throwable { tmp[0] = 1; } } 
This time the picture is different:
$ java -Xms16m -Xmx16m Test
Total: 16252928; free: 15965064
Total: 16252928; free: 14003496
Total: 16252928; free: 12001896
LongFinalize finalizer
Total: 16252928; free: 9996648
Total: 16252928; free: 7987144
Total: 16252928; free: 6459728
Total: 16252928; free: 4458128
Total: 16252928; free: 6357016
Total: 16252928; free: 4347352
Total: 16252928; free: 2331112
Total: 16252928; free: 329512
Exception in thread "main" java.lang.OutOfMemoryError: Java heap space
at Test$BigObject.<init>(Test.java:12)
at Test.main(Test.java:31)


Note that once the memory has become more: those BigObjects that were destroyed for finalize () before LongFinalize.finalizer () were destroyed.

I wrote above that only objects that do not have finalize () are regularly deleted in such conditions. In fact, it is enough that the finalize () method is empty. The garbage collector adds an object to the Finalizer queue only if there is code in the finalize () body. For example, we can create a child class with an empty finalize ():
  static class SubBigObject extends BigObject { protected void finalize() throws Throwable { } } 
And create child class objects in main () (replace new BigObject () with new SubBigObject ()). We will see that garbage collection is successful again.

Thus, you can speed up the destruction of objects and even protect against a hung thread Finalizer if you subclass with an empty finalize () and create only child objects. Of course, you should be aware of what you are doing: if finalize () was written, it was probably necessary for something. And yet do not write finalize () without extreme need. For example, it would seem that in the abstract class InputStream you could make finalize (), calling close () to make it more convenient. In fact, finalize () is defined only in those child classes that work directly with system resources (for example, FileInputStream). And, say, in BufferedInputStream finalize () is not needed, even if it wraps a FileInputStream. Here, excessive versatility is harmful. If the author of a library, through thoughtlessness, made unnecessary finalize () in an abstract class, and you are not working with system resources, redefine it with an empty body in your implementation. After all, even if Finalizer does not freeze, it can simply not cope with the flow of objects being freed, which will lead to a significant slowdown in their removal and heap growth.

It should also be said about such a thing as System.runFinalization (). This call creates a second stream, the SecondaryFinalizer, which also calls finalize () for objects from the same queue. In this case, the thread that called System.runFinalization () waits until the Finalizer queue, which is currently available, ends. In principle, it can save you from OutOfMemory if the main Finalizer is frozen. Let's go back to the version of the program without SubBigObject and add this call if there is not enough memory. So that you are not confused, I will give the full text:
 public final class Test { static class LongFinalize { protected void finalize() throws Throwable { System.out.println("LongFinalize finalizer"); Thread.sleep(10000000); } } static class BigObject { char[] tmp = new char[10000]; protected void finalize() throws Throwable { tmp[0] = 1; } } public static void main(String... args) { int i=0; new LongFinalize(); while(true) { new BigObject(); try { Thread.sleep(10); } catch( InterruptedException e ) {} if(i++%100==0) System.out.println("Total: "+Runtime.getRuntime().totalMemory()+ "; free: "+Runtime.getRuntime().freeMemory()); if(Runtime.getRuntime().freeMemory()<1e6) System.runFinalization(); } } } 

Let's look at the result of the work:
$ java -Xms16m -Xmx16m Test
Total: 16252928; free: 15965064
Total: 16252928; free: 14003496
Total: 16252928; free: 12001896
LongFinalize finalizer
Total: 16252928; free: 9996648
Total: 16252928; free: 7987144
Total: 16252928; free: 6459832
Total: 16252928; free: 4458232
Total: 16252928; free: 6357120
Total: 16252928; free: 4347456
Total: 16252928; free: 2331216
Total: 16252928; free: 239072
Total: 16252928; free: 11729800
Total: 16252928; free: 9717584
Total: 16252928; free: 7719416
Total: 16252928; free: 5710768
Total: 16252928; free: 3721880
Total: 16252928; free: 1710824
Total: 16252928; free: 11261488


The program continues to live, despite the fact that the main Finalizer () is hanging. Of course, this will not save you if there are many objects in the queue with a long finalize (), and in general an explicit call to System.runFinalization () in the program rather indicates that something is wrong.

Source: https://habr.com/ru/post/144544/


All Articles