HotSpot SA #3:FinalizerInfo,
前面我們已經把玩過SA工具中的JStack和ClassDump,今天再來看個好玩的,FinalizerInfo。
Object#finalize
看名字多半能猜到,FinalizerInfo是跟Object的finalize方法的執行有關的,
Called by the garbage collector on an object when garbage collection determines that there are no more references to the object.
然後我翻了好久的官方文檔才找到這篇Troubleshooting Guide for HotSpot VM中的這一小節有對finalize相關實現的描述 ,
One other potential source of OutOfMemoryError arises with applications that make excessive use of finalizers. If a class has a finalize method, then objects of that type do not have their space reclaimed at garbage collection time. Instead, after garbage collection the objects are queued for finalization, which occurs at a later time. In the Sun implementation, finalizers are executed by a daemon thread that services the finalization queue. If the finalizer thread cannot keep up with the finalization queue, then the Java heap could fill up and OutOfMemoryError would be thrown.
也就是說,在對象被回收之前,需要執行finalize方法,而finalize方法的執行又是需要排著隊由某個線程來一個個消費的。下面我們通過會阻塞住的finalize方法來驗證看看,
private static class Foo { @Override protected void finalize() throws Throwable { System.out.println("finalize#" + this); super.finalize(); System.in.read(); // 這個finalize方法將會卡住 } } private static class Bar { @Override protected void finalize() throws Throwable { System.out.println("finalize#" + this); super.finalize(); System.in.read(); // 這個finalize方法也會卡住 } } public static void main(String[] args) throws Exception{ foo(); bar(); System.gc(); Thread.sleep(2000); System.in.read(); } private static Foo foo() { return new Foo(); } private static Bar bar() { return new Bar(); }
如果上述沒錯,那麼Foo跟Bar只要其中一個的finalize方法執行了,另一個必定得不到執行,因為單個隊列,有一個卡住了那麼其後續的必然也無法被消費了。
事實確實如此,輸出只有一行finalize#me.kisimple.just4fun.Main$Bar@571688,所以Foo必定是在等待著被finalize。這時候FinalizerInfo就派上用場了,用它我們可以觀察VM中有哪些正在等待被finalize的對象,
# java7 sun.jvm.hotspot.tools.FinalizerInfo 5960Attaching to process ID 5960, please wait...Debugger attached successfully.Server compiler detected.JVM version is 24.65-b04Number of objects pending for finalization: 33Count Class description-------------------------------------------------------17 java.util.zip.ZipFile$ZipFileInputStream11 java.util.zip.ZipFile$ZipFileInflaterInputStream4 java.io.FileInputStream1 me.kisimple.just4fun.Main$Foo
妥妥的我們看到了1 me.kisimple.just4fun.Main$Foo,驗證了文檔中的描述。
FinalizerThread
更進一步,我們可以衝進FinalizerInfo的源碼看看,
/* * The implementation here has a dependency on the implementation of * java.lang.ref.Finalizer. If the Finalizer implementation changes it's * possible this method will require changes too. We looked into using * ObjectReader to deserialize the objects from the target VM but as * there aren't any public methods to traverse the queue it means using * reflection which will also tie us to the implementation. * * The assumption here is that Finalizer.queue is the ReferenceQueue * with the objects awaiting finalization. The ReferenceQueue queueLength * is the number of objects in the queue, and 'head' is the head of the * queue. */
注釋就已經告訴我們,存放等待finalize的對象的隊列就是在java.lang.ref.Finalizer.queue。然後去看看Finalizer的源碼,可以看到消費這個queue的線程,也就是Finalizer.FinalizerThread線程,
public void run() { if (running) return; // Finalizer thread starts before System.initializeSystemClass // is called. Wait until JavaLangAccess is available while (!VM.isBooted()) { // delay until VM completes initialization try { VM.awaitBooted(); } catch (InterruptedException x) { // ignore and continue } } final JavaLangAccess jla = SharedSecrets.getJavaLangAccess(); running = true; for (;;) { try { Finalizer f = (Finalizer)queue.remove(); f.runFinalizer(jla); } catch (InterruptedException x) { // ignore and continue } } }
private void runFinalizer(JavaLangAccess jla) { synchronized (this) { if (hasBeenFinalized()) return; remove(); } try { Object finalizee = this.get(); if (finalizee != null && !(finalizee instanceof java.lang.Enum)) { /////////////////////////////////////////////// // 最後由JavaLangAccess來真正執行Object#finalize了 jla.invokeFinalize(finalizee); /* Clear stack slot containing this variable, to decrease the chances of false retention with a conservative GC */ finalizee = null; } } catch (Throwable x) { } super.clear(); }
還有另一個方法,我們可以在FinalizerThread打斷點進行調試,這樣也是能驗證我們的想法的。alright,今天就先到這吧^_^
參考資料
- http://openjdk.java.net/jeps/132
- http://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-VM/html/memleaks.html#gbyvh