加入收藏 | 设为首页 | 会员中心 | 我要投稿 李大同 (https://www.lidatong.com.cn/)- 科技、建站、经验、云计算、5G、大数据,站长网!
当前位置: 首页 > 编程开发 > Java > 正文

Debugging to Understand Finalizer--reference

发布时间:2020-12-14 06:18:10 所属栏目:Java 来源:网络整理
导读:This post is covering one of the Java built-in concepts called? Finalizer . This concept is actually both well-hidden and well-known,depending whether you have bothered to take a look at the? java.lang.Object ?class thoroughly enough. Righ

This post is covering one of the Java built-in concepts called?Finalizer. This concept is actually both well-hidden and well-known,depending whether you have bothered to take a look at the?java.lang.Object?class thoroughly enough. Right in the?java.lang.Object?itself,there is a method called?finalize(). The implementation of the method is empty,but both the power and dangers lie on the JVM internal behaviour based upon the presence of such method.

When JVM detects that class has a?finalize()?method,magic starts to happen. So,lets go forward and create a class with a non-trivial?finalize()?method so we can see how differently JVM is handling objects in this case. For this,lets start by constructing an example program:

Example of Finalizable class

?
?
??
???
???
??
?
?
?

The example is creating new objects in an unterminated loop. These objects use static aliveCount variable to keep track how many instances have already been created. Whenever a new instance is created,the counter is incremented and whenever the?finalize()?is called after GC,the counter value is reduced.

So what would you expect from such a simple code snippet? As the newly created objects are not referenced from anywhere,they should be immediately eligible for GC. So you might expect the code to run forever with the output of the program to be something similar to the following:

??
?
?
?

Apparently this is not the case. The reality is completely different,for example in my Mac OS X on JDK 1.7.0_51,I see the program failing with??just about after ~1.2M objects have been created:

?
?
?
?
?

Garbage Colletion behaviour

To understand what is happening,we would need to take a look at our example code during the runtime. For this,lets run our example with?-XX:+PrintGCDetails?flag turned on:

??
??

From the logs we see that after just a few minor GCs cleaning Eden,the JVM turns to a lot more expensive Full GC cycles cleaning tenured and old space. Why so? As nothing is referring our objects,shouldn’t all the instances die young in Eden? What is wrong with our code?

To understand,the reasons for GC behaving as it does,let us do just a minor change to the code and remove the body of the?finalize()?method. Now the JVM detects that our class does not need to be finalized and changes the behaviour back to “normal”. Looking at the GC logs we would see only cheap minor GCs running forever.

java memory eden permgen tenured young

In our original example on the other hand,the situation is different. Instead of objects without any references,?JVM creates a personal watchdog for each and every one of the?Finalizableinstances. This watchdog is an instance of?Finalizer. And all those instances in turn are referenced by the?Finalizer?class. So due to this reference chain,the whole gang stays alive.

Now with the Eden full and all objects being referenced,GC has no other alternatives than to copy everything into Survivor space. Or worse,if the free space in Survivor is also limited,then expand to the Tenured space. As you might recall,GC in Tenured space is a completely different beast and is a lot more expensive than “lets throw away everything” approach used to clean Eden.

Finalizer queue

Only after the GC has finished,JVM understands that apart from the Finalizers nothing refers to our instances,so it can mark all Finalizers pointing to those instances to be ready for processing. So the GC internals add all Finalizer objects to a special queue atjava.lang.ref.Finalizer.ReferenceQueue.

Only when all this hassle is completed our application threads can proceed with the actual work. One of those threads is now particularly interesting for us – the?“Finalizer”?daemon thread. You can see this thread in action by taking a thread dump via jstack:

<a class="item printSource" title="print" href="http://java.dzone.com/articles/debugging-understand-finalizer#printSource"&gt;print<a class="item about" title="?" href="http://java.dzone.com/articles/debugging-understand-finalizer#about"&gt;?

??
?
??

From the above we see the?“Finalizer”?daemon thread running.?“Finalizer”?thread is a ?thread with just a single responsibility. The thread runs an unterminated loop blocked waiting for new instances to appear in?java.lang.ref.Finalizer.ReferenceQueue?queue. Whenever the?“Finalizer”?threads detects new objects in the queue,it pops the object,calls the?finalize()?method and removes the reference from?Finalizer?class,so the next time the GC runs the?Finalizer?and the referenced object can now be GCd.

So we have two unterminated loops now running in two different threads. Our main thread is busy creating new objects. Those objects all have their personal watchdogs called?Finalizer?which are being added to the?java.lang.ref.Finalizer.ReferenceQueue?by the GC. And the “Finalizer” thread is processing this queue,popping all the instances from this queue and calling the?finalize()?methods on the instances.

Most of the time you would get away with this. Calling the?finalize()?method should complete faster than we actually create new instances. ?So in many cases,the?“Finalizer”?thread would be able to catch up and empty the queue before the next GC pours more?Finalizers?into it. In our case,it is apparently not happening.

Why so? The?“Finalizer”?thread is run at a lower priority than the main thread. In means that it gets less CPU time and is thus not able to catch up with the pace objects are being created. And here we have it – the objects are created faster than the?“Finalizer”?thread is able to?finalize()?them,causing all the available heap to be consumed. Result – different flavours of our dear friend.

If you still do not believe me,take a heap dump and take a look inside. For example,when our code snipped is launched with?-XX:+HeapDumpOnOutOfMemoryError?parameter,I see a following picture in Eclipse MAT Dominator Tree:

Eclipse MAT dominator tree

Finalizers.

Conclusions

So to recap,the lifecycle of?Finalizable?objects is completely different from the standard behaviour,namely:

  • The JVM will create the instance of?Finalizable?object
  • The JVM will create an instance of the?java.lang.ref.Finalizer,pointing to our newly created object instance.
  • java.lang.ref.Finalizer?class holds on to the?java.lang.ref.Finalizer?instance that was just created. This blocks next minor GC from collecting our objects and is keeping them alive.
  • Minor GC is not able to clean the Eden and expands to Survivor and/or Tenured spaces.
  • GC detects that the objects are eligible for finalizing and adds those objects to thejava.lang.ref.Finalizer.ReferenceQueue
  • The queue will be processed by “Finalizer” thread,popping the objects one-by-one and calling their?finalize()?methods.
  • After?finalize()?is called,the “Finalizer” thread removes the reference from Finalizer class,so during the next GC the objects are eligible to be GCd.
  • The “Finalizer” thread competes with our “main” thread,but due to lower priority gets less CPU time and is thus never able to catch up.
  • The program exhausts all available resources and throws?.

Moral of the story? Next time,when you consider?finalize()?to be superior to the usual cleanup,teardown or finally blocks,think again. You might be happy with the clean code you produced,but the ever-growing queue of?Finalizable?objects thrashing your tenured and old generations might indicate the need to reconsider.

reference from :http://java.dzone.com/articles/debugging-understand-finalizer

(编辑:李大同)

【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容!

    推荐文章
      热点阅读