一、原因
查找资料后发现,该异常在5.0以上出现频率最高,其原因在ART虚拟机的垃圾回收机制中。
ART虚拟机垃圾回收共有5个相关守护线程:
public final class Daemons {
private static final int NANOS_PER_MILLI = 1000 * 1000;
private static final int NANOS_PER_SECOND = NANOS_PER_MILLI * 1000;
private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND;
public static void start() {
ReferenceQueueDaemon.INSTANCE.start();
FinalizerDaemon.INSTANCE.start();
FinalizerWatchdogDaemon.INSTANCE.start();
HeapTrimmerDaemon.INSTANCE.start();
GCDaemon.INSTANCE.start();
}
}
问题出在 FinalizerWatchdogDaemon 这个守护线程中,在GC时,复写了 finalize() 方法的对象并不会立刻被回收,而是被放入队列依次执行 finalize() 方法,FinalizerWatchdogDaemon 这个守护线程就是去监视 finalize() 方法是否耗时,如果超过了最大阈值,那么抛出异常。而在锁屏后系统进入休眠模式,执行方法时间很容易超过超时阈值。
抛出异常的代码在此:
private static void finalizerTimedOut(Object object) {
// The current object has exceeded the finalization deadline; abort!
String message = object.getClass().getName() + ".finalize() timed out after "
+ (MAX_FINALIZE_NANOS / NANOS_PER_SECOND) + " seconds";
Exception syntheticException = new TimeoutException(message);
// We use the stack from where finalize() was running to show where it was stuck.
syntheticException.setStackTrace(FinalizerDaemon.INSTANCE.getStackTrace());
Thread.UncaughtExceptionHandler h = Thread.getDefaultUncaughtExceptionHandler();
if (h == null) {
// If we have no handler, log and exit.
System.logE(message, syntheticException);
System.exit(2);
}
// Otherwise call the handler to do crash reporting.
// We don't just throw because we're not the thread that
// timed out; we're the thread that detected it.
h.uncaughtException(Thread.currentThread(), syntheticException);
}
可以看到,阈值由 MAX_FINALIZE_NANOS / NANOS_PER_SECOND 计算出来,正好是 10 。
二、修复
在锁屏后,我们可以通过反射修改 Daemons 类的 MAX_FINALIZE_NANOS 值,在开屏后修改回来。
本机验证后通过,代码如下:
try {
Class<?> c = Class.forName("java.lang.Daemons");
Field maxField = c.getDeclaredField("MAX_FINALIZE_NANOS");
maxField.setAccessible(true);
long oldValue = (long) maxField.get(null);
maxField.set(null, Long.MAX_VALUE);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
由于 MAX_FINALIZE_NANOS 本身需要经过计算才能获得结果(10L * NANOS_PER_SECOND),所以系统在调用时并不会受Java 虚拟机内联的影响,从而正确调用到我们修改后的值。
网友评论