1. 背景
以前在解anr问题的时候常常会看trace,经常看到无响应进程有类似以下的trace堆栈,但是没有机会去了解以下这些守护线程。
正好最近我们在虚拟机回收内存的时候做了一些逻辑,所以简单分析一下。
"ReferenceQueueDaemon" daemon prio=5 tid=10 Waiting
| group="system" sCount=1 dsCount=0 flags=1 obj=0x12dc03d8 self=0x73e5721800
| sysTid=6355 nice=4 cgrp=default sched=0/0 handle=0x73876a7d50
| state=S schedstat=( 5968594 3597186 25 ) utm=0 stm=0 core=1 HZ=100
| stack=0x73875a5000-0x73875a7000 stackSize=1039KB
| held mutexes=
at java.lang.Object.wait(Native method)
- waiting on <0x00c7e21d> (a java.lang.Class<java.lang.ref.ReferenceQueue>)
at java.lang.Object.wait(Object.java:442)
at java.lang.Object.wait(Object.java:568)
at java.lang.Daemons$ReferenceQueueDaemon.runInternal(Daemons.java:218)
- locked <0x00c7e21d> (a java.lang.Class<java.lang.ref.ReferenceQueue>)
at java.lang.Daemons$Daemon.run(Daemons.java:140)
at java.lang.Thread.run(Thread.java:919)
"FinalizerDaemon" daemon prio=5 tid=11 Waiting
| group="system" sCount=1 dsCount=0 flags=1 obj=0x12dc0450 self=0x73e5723400
| sysTid=6356 nice=4 cgrp=default sched=0/0 handle=0x738659ed50
| state=S schedstat=( 6188228 2839739 24 ) utm=0 stm=0 core=1 HZ=100
| stack=0x738649c000-0x738649e000 stackSize=1039KB
| held mutexes=
at java.lang.Object.wait(Native method)
- waiting on <0x0b6e4c92> (a java.lang.Object)
at java.lang.Object.wait(Object.java:442)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:190)
- locked <0x0b6e4c92> (a java.lang.Object)
at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:211)
at java.lang.Daemons$FinalizerDaemon.runInternal(Daemons.java:274)
at java.lang.Daemons$Daemon.run(Daemons.java:140)
at java.lang.Thread.run(Thread.java:919)
"FinalizerWatchdogDaemon" daemon prio=5 tid=12 Waiting
| group="system" sCount=1 dsCount=0 flags=1 obj=0x12dc04c8 self=0x73e572a000
| sysTid=6357 nice=4 cgrp=default sched=0/0 handle=0x7385495d50
| state=S schedstat=( 2227916 6292450 28 ) utm=0 stm=0 core=1 HZ=100
| stack=0x7385393000-0x7385395000 stackSize=1039KB
| held mutexes=
at java.lang.Object.wait(Native method)
- waiting on <0x04374663> (a java.lang.Daemons$FinalizerWatchdogDaemon)
at java.lang.Object.wait(Object.java:442)
at java.lang.Object.wait(Object.java:568)
at java.lang.Daemons$FinalizerWatchdogDaemon.sleepUntilNeeded(Daemons.java:342)
- locked <0x04374663> (a java.lang.Daemons$FinalizerWatchdogDaemon)
at java.lang.Daemons$FinalizerWatchdogDaemon.runInternal(Daemons.java:322)
at java.lang.Daemons$Daemon.run(Daemons.java:140)
at java.lang.Thread.run(Thread.java:919)
2. 分析
直接先简单介绍一下,FinalizerWatchdogDaemon,FinalizerDaemon和ReferenceQueueDaemon三个类都继承了Daemon类,并实现了Daemon类的runInternal方法。Daemon类的run方法会执行对应子类的runInternal方法,简单看了一下Daemon守护的start方法就会调用runInternal方法,这些守护线程开启的时候都会执行对应的runInternal方法。
在这三个守护线程开启后,就会走到上面的“堆栈”显示的状态。
三个守护线程wait的地方,具体是:
- FinalizerDaemon, libcore/libart/src/main/java/java/lang/Daemons.java, finalizingReference = (FinalizerReference<?>)queue.remove();
- ReferenceQueueDaemon, libcore/libart/src/main/java/java/lang/Daemons.java, ReferenceQueue.class.wait();
- FinalizerWatchdogDaemon, libcore/libart/src/main/java/java/lang/Daemons.java, sleepUntilNeeded()
这三个守护线程都调用的Object.wait方法等待,其分别等待的锁对象是:
- FinalizerDaemon, 等待java/lang/ref/ReferenceQueue.java的lock对象;
- ReferenceQueueDaemon,等待ReferenceQueue的类对象;
- FinalizerWatchdogDaemon,等待FinalizerWatchdogDaemon的的实例对象;
3. runFinalization
简单追溯代码之后,发现System.runFinalization方法可以notify上面三个守护线程。我是反向追溯的,但是下面代码的分析还是正向来贴把。
java/lang/System.java runFinalization()
--->java/lang/Runtime.java runFinalization()
--->libcore/libart/src/main/java/dalvik/system/VMRuntime.java runFinalization()
--->java/lang/ref/FinalizerReference.java enqueueSentinelReference(sentinel)
--->java/lang/ref/ReferenceQueue.java static void add(Reference<?> list)
在java/lang/ref/ReferenceQueue.java的add的方法中,会调用“ReferenceQueue.class.notifyAll()”唤醒等待在ReferenceQueue类对象的所有线程,其中就有ReferenceQueueDaemon线程。
而ReferenceQueueDaemon被唤醒之后,又会唤醒等待在ReferenceQueue.java的lock对象的FinalizerDaemon线程。接着会调用“FinalizerWatchdogDaemon.INSTANCE.wakeUp()”唤醒FinalizerWatchdogDaemon线程。
最后,当FinalizerWatchdogDaemon守护线程超时之后,则会调用finalizerTimedOut方法做超时后的逻辑。
真正干活的是FinalizerDaemon线程,做Finalize的逻辑在
libcore/libart/src/main/java/java/lang/Daemons.java doFinalize(finalizingReference);
java/lang/Daemons.java object.finalize();
4.结语
其实在进行这部分代码的追溯之前,我也没想到会追溯到runFinalization方法。
但是工作中经常需要在各个代码块中加逻辑,有时候加了一些逻辑之后,顺便解决一下之前工作中的困惑,也是挺好的。
最后提一点,网上有人喜欢在runFinalization之前先调用一下gc方法,其实没有必要,在runFinalization方法的内部,会帮你gc了。