前面我们已经把玩过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#[email protected]
,所以Foo必定是在等待着被finalize。这时候FinalizerInfo就派上用场了,用它我们可以观察VM中有哪些正在等待被finalize的对象,
# java7 sun.jvm.hotspot.tools.FinalizerInfo 5960
Attaching to process ID 5960, please wait...
Debugger attached successfully.
Server compiler detected.
JVM version is 24.65-b04
Number of objects pending for finalization: 33
Count Class description
-------------------------------------------------------
17 java.util.zip.ZipFile$ZipFileInputStream
11 java.util.zip.ZipFile$ZipFileInflaterInputStream
4 java.io.FileInputStream
1 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