Android TimeoutException崩溃学习笔记

主要引发原因

Java对象的finalize()方法处理超时。
Java的Object对象中,有一个finalize()方法,默认实现为空。

public class Object {
	// ……
    protected void finalize() throws Throwable { }
}

finalize()的官方Doc(android-28)如下:

Called by the garbage collector on an object when garbage collection determines that there are no more references to the object. A subclass overrides the finalize method to dispose of system resources or to perform other cleanup.
The general contract of finalize is that it is invoked if and when the JavaTM virtual machine has determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, except as a result of an action taken by the finalization of some other object or class which is ready to be finalized. The finalize method may take any action, including making this object available again to other threads; the usual purpose of finalize, however, is to perform cleanup actions before the object is irrevocably discarded. For example, the finalize method for an object that represents an input/output connection might perform explicit I/O transactions to break the connection before the object is permanently discarded.
The finalize method of class Object performs no special action; it simply returns normally. Subclasses of Object may override this definition.
The Java programming language does not guarantee which thread will invoke the finalize method for any given object. It is guaranteed, however, that the thread that invokes finalize will not be holding any user-visible synchronization locks when finalize is invoked. If an uncaught exception is thrown by the finalize method, the exception is ignored and finalization of that object terminates.
After the finalize method has been invoked for an object, no further action is taken until the Java virtual machine has again determined that there is no longer any means by which this object can be accessed by any thread that has not yet died, including possible actions by other objects or classes which are ready to be finalized, at which point the object may be discarded.
The finalize method is never invoked more than once by a Java virtual machine for any given object.
Any exception thrown by the finalize method causes the finalization of this object to be halted, but is otherwise ignored.

第一段大意是:当垃圾回收器确定没有对该对象的更多引用时,由垃圾回收器调用。子类重写finalize()方法以处置系统资源或执行其他清理工作。
如果该方法中存在耗时操作,超过系统定义的超时时间(默认10秒,厂商也可能修改),就会抛出TimeoutException异常。

Android SDK中有一个finalize操作守护相关的类——Daemons.java
其中包含4个守护相关的线程定义,是Daemons的静态内部类。类图如下:
在这里插入图片描述
Daemons类主要代码:

/**
 * Calls Object.finalize() on objects in the finalizer reference queue. The VM
 * will abort if any finalize() call takes more than the maximum finalize time
 * to complete.
 *
 * @hide
 */
public final class Daemons {
    private static final int NANOS_PER_MILLI = 1000 * 1000;
    private static final int NANOS_PER_SECOND = NANOS_PER_MILLI * 1000;
    // 手机厂商可能会修改这个时间,所以不一定是10秒
    private static final long MAX_FINALIZE_NANOS = 10L * NANOS_PER_SECOND;

    public static void start() {
        ReferenceQueueDaemon.INSTANCE.start();
        FinalizerDaemon.INSTANCE.start();
        FinalizerWatchdogDaemon.INSTANCE.start();
        HeapTaskDaemon.INSTANCE.start();
    }

    public static void startPostZygoteFork() {
        ReferenceQueueDaemon.INSTANCE.startPostZygoteFork();
        FinalizerDaemon.INSTANCE.startPostZygoteFork();
        FinalizerWatchdogDaemon.INSTANCE.startPostZygoteFork();
        HeapTaskDaemon.INSTANCE.startPostZygoteFork();
    }

    public static void stop() {
        HeapTaskDaemon.INSTANCE.stop();
        ReferenceQueueDaemon.INSTANCE.stop();
        FinalizerDaemon.INSTANCE.stop();
        FinalizerWatchdogDaemon.INSTANCE.stop();
    }
    // 省略其他代码
    private static abstract class Daemon implements Runnable {
    	private Thread thread;
	    // 省略其他代码
    }
    
    private static class ReferenceQueueDaemon extends Daemon {
        private static final ReferenceQueueDaemon INSTANCE = new ReferenceQueueDaemon();
        // 省略其他代码
    }
    
    private static class FinalizerDaemon extends Daemon {
        private static final FinalizerDaemon INSTANCE = new FinalizerDaemon();
        // 省略其他代码
    }
    
    /**
     * The watchdog exits the VM if the finalizer ever gets stuck. We consider
     * the finalizer to be stuck if it spends more than MAX_FINALIZATION_MILLIS
     * on one instance.
     */
    private static class FinalizerWatchdogDaemon extends Daemon {
        private static final FinalizerWatchdogDaemon INSTANCE = new FinalizerWatchdogDaemon();
        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());

            // Send SIGQUIT to get native stack traces.
            try {
                Os.kill(Os.getpid(), OsConstants.SIGQUIT);
                // Sleep a few seconds to let the stack traces print.
                Thread.sleep(5000);
            } catch (Exception e) {
                System.logE("failed to send SIGQUIT", e);
            } catch (OutOfMemoryError ignored) {
                // May occur while trying to allocate the exception.
            }
        }
        // 省略其他代码
    }
    private static class HeapTaskDaemon extends Daemon {
        private static final HeapTaskDaemon INSTANCE = new HeapTaskDaemon();
        // 省略其他代码
    }
    // 省略其他代码
}

复现

手动复现一个TimeoutException也比较简单,定义一个类,在finalize()方法中模拟耗时操作:

public class FinlaizeTimeoutObject {
    @Override
    protected void finalize() throws Throwable {
        super.finalize();
        // 每个手机触发 Timeout 的时长不同,看情况修改
        Thread.sleep(30000);
    }
}

然后通过点击按钮触发,等待一段时间后观察。

private void genFinlaizeTimeout() {
	FinlaizeTimeoutObject timeoutObj = new FinlaizeTimeoutObject();
}

private void onBtnClick() {
	genFinlaizeTimeout();
	Runtime.getRuntime().gc();
    System.runFinalization();
}

不出意外,IDE的logcat就会打印出如下异常:

E/AndroidRuntime: FATAL EXCEPTION: FinalizerWatchdogDaemon
    Process: com.sy.misctest, PID: 31765
    java.util.concurrent.TimeoutException: com.sy.misctest.FinlaizeTimeoutObject.finalize() timed out after 10 seconds
        at java.lang.Thread.sleep(Native Method)
        at java.lang.Thread.sleep(Thread.java:1031)
        at java.lang.Thread.sleep(Thread.java:985)
        at com.sy.misctest.FinlaizeTimeoutObject.finalize(FinlaizeTimeoutObject.java:8)
        at java.lang.Daemons$FinalizerDaemon.doFinalize(Daemons.java:229)
        at java.lang.Daemons$FinalizerDaemon.run(Daemons.java:207)
        at java.lang.Thread.run(Thread.java:818)

解决方法

通过反射,将FinalizerWatchdogDaemon的thread属性置为null,禁止FinalizerWatchdogDaemon计时和超时抛出异常。

public static void disableWatchDog() {
        // Android P 以后不能反射FinalizerWatchdogDaemon
        if (Build.VERSION.SDK_INT >= 28) {
            Log.w(TAG, "stopWatchDog, do not support after Android P, just return");
            return;
        }
        try {
            final Class clazz = Class.forName("java.lang.Daemons$FinalizerWatchdogDaemon");
            final Field field = clazz.getDeclaredField("INSTANCE");
            field.setAccessible(true);
            final Object watchdog = field.get(null);
            try {
                final Field thread = clazz.getSuperclass().getDeclaredField("thread");
                thread.setAccessible(true);
                thread.set(watchdog, null);
            } catch (final Throwable t) {
                t.printStackTrace();
                try {
                    // 直接调用stop方法,在Android 6.0之前会有线程安全问题
                    final Method method = clazz.getSuperclass().getDeclaredMethod("stop");
                    method.setAccessible(true);
                    method.invoke(watchdog);
                } catch (final Throwable e) {
                    t.printStackTrace();
                }
            }
        } catch (final Throwable t) {
            t.printStackTrace();
        }
    }

但是,这种解决方式,其实治标不治本,只是抑制了finalize()超时引发的异常。要从源头解决该问题,还是应该根据实际情况分析,是否因为操作不当,在finalize()中执行了耗时操作。在应用中可以结合减少耗时操作和这种抑制finalize()超时异常的方式。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值