程序的崩溃处理UncaughtExceptionHandler

本文深入探讨Java中如何处理未捕获的异常,包括通过设置线程的UncaughtExceptionHandler来记录异常堆栈信息的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

引言

一般程序崩溃时会退出,同时在控制台输出崩溃时的堆栈信息,如下代码:

public class Test17 {
 
    public static void main(String[] args) {
        getA();
    }
 
    private static int getA() {
        return getB();
    }
 
    private static int getB() {
        return getC();
    }
 
    private static int getC() {
        return 10 0;
    }
}

运行后,控制台输出:

Exception in thread "main" java.lang.ArithmeticException: / by zero
    at Test17.getC(Test17.java:16)
    at Test17.getB(Test17.java:12)
    at Test17.getA(Test17.java:8)
    at Test17.main(Test17.java:4)

引发问题的地方在最上面,往下依次是方法调用的链条。

扩展应用

很多时候,我们希望对崩溃进行一些处理,比如将崩溃堆栈保存到文件,或是发送到服务器进行统一分析,例子如下:

import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.lang.Thread.UncaughtExceptionHandler;
import java.text.SimpleDateFormat;
import java.util.Date;
 
public class Test17 {
 
    public static void main(String[] args) {
        Thread.currentThread().setUncaughtExceptionHandler(new MyHandler());
        getA();
    }
 
    private static int getA() {
        return getB();
    }
 
    private static int getB() {
        return getC();
    }
 
    private static int getC() {
        return 10 0;
    }
}
 
class MyHandler implements UncaughtExceptionHandler {
 
    @Override
    public void uncaughtException(Thread t, Throwable e) {
        try {
            FileWriter fw = new FileWriter("log.txt"true);
 
            fw.append(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")
                    .format(new Date()) + "\n");
            fw.append(t.toString() + "\n");
 
            StringWriter sw = new StringWriter();
            e.printStackTrace(new PrintWriter(sw));
            fw.append(sw.toString());
 
            fw.flush();
            fw.close();
        catch (IOException e1) {
            e1.printStackTrace();
        }
    }
}

查看log.txt文件:

2012-05-10 11:47:13
Thread[main,5,main]
java.lang.ArithmeticException: / by zero
        at Test17.getC(Test17.java:26)
        at Test17.getB(Test17.java:22)
        at Test17.getA(Test17.java:18)
        at Test17.main(Test17.java:14)

其中Thread[main,5,main],前面main为线程名字,5为优先级(1~10,5为一般优先级),后面main为线程组(ThreadGroup)名字,ThreadGroup采用树形结构,一个树根发散成n叉树,一个ThreadGroup包含1个或多个Thread,《Thinking in Java》对ThreadGroup的设计进行了批判,从使用者的角度看,可以适当了解一下,不必深究其中的设计思想,不过作者说的挺有意思“不承认错误是让别人承担后果,承认错误是让自己承担后果”,类似还拿Vector和ArrayList,Hashtable和HashMap做例子,有兴趣的可以看看其中的章节。

分析

源代码中Thread.java的UncaughtExceptionHandler接口注释说明如下:

// Added in JSR-166
 
    /**
     * Interface for handlers invoked when a <tt>Thread</tt> abruptly
     * terminates due to an uncaught exception.
     * <p>When a thread is about to terminate due to an uncaught exception
     * the Java Virtual Machine will query the thread for its
     * <tt>UncaughtExceptionHandler</tt> using
     * {@link #getUncaughtExceptionHandler} and will invoke the handler's
     * <tt>uncaughtException</tt> method, passing the thread and the
     * exception as arguments.
     * If a thread has not had its <tt>UncaughtExceptionHandler</tt>
     * explicitly set, then its <tt>ThreadGroup</tt> object acts as its
     * <tt>UncaughtExceptionHandler</tt>. If the <tt>ThreadGroup</tt> object
     * has no
     * special requirements for dealing with the exception, it can forward
     * the invocation to the {@linkplain #getDefaultUncaughtExceptionHandler
     * default uncaught exception handler}.
     *
     * @see #setDefaultUncaughtExceptionHandler
     * @see #setUncaughtExceptionHandler
     * @see ThreadGroup#uncaughtException
     * @since 1.5
     */
    public interface UncaughtExceptionHandler {
        /**
         * Method invoked when the given thread terminates due to the
         * given uncaught exception.
         * <p>Any exception thrown by this method will be ignored by the
         * Java Virtual Machine.
         * @param t the thread
         * @param e the exception
         */
        void uncaughtException(Thread t, Throwable e);
    }

就是说程序崩溃时,虚拟机会查询线程的uncaught exception handler,由其处理崩溃的事情。其实Thread有两个变量:

// null unless explicitly set
private volatile UncaughtExceptionHandler uncaughtExceptionHandler;
 
// null unless explicitly set
private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler;

注意 defaultUncaughtExceptionHandler 是静态变量,是对整个Thread而言的,另外注意注释,如果没有显式地设置,将会为null,也就是说没有调用

public static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    SecurityManager sm = System.getSecurityManager();
    if (sm != null) {
        sm.checkPermission(
            new RuntimePermission("setDefaultUncaughtExceptionHandler")
                );
    }
 
     defaultUncaughtExceptionHandler = eh;
 }
 
public void setUncaughtExceptionHandler(UncaughtExceptionHandler eh) {
    checkAccess();
    uncaughtExceptionHandler = eh;
}

的话,就没有handler进行处理。

程序崩溃流程如下:

  1. 系统分发崩溃消息
    /**
     * Dispatch an uncaught exception to the handler. This method is
     * intended to be called only by the JVM.
     */
    private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
  2. 根据当前线程判断交由谁处理,如果有uncaughtExceptionHandler,则由它处理,如果没有,则由当前线程的ThreadGroup处理。
    /**
     * Returns the handler invoked when this thread abruptly terminates
     * due to an uncaught exception. If this thread has not had an
     * uncaught exception handler explicitly set then this thread's
     * <tt>ThreadGroup</tt> object is returned, unless this thread
     * has terminated, in which case <tt>null</tt> is returned.
     * @since 1.5
     */
    public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group;
    }
  3. ThreadGroup实现了UncaughtExceptionHandler,处理过程如下:
    public void uncaughtException(Thread t, Throwable e) {
        if (parent != null) {
            parent.uncaughtException(t, e);
        else {
            Thread.UncaughtExceptionHandler ueh = Thread
                    .getDefaultUncaughtExceptionHandler();
            if (ueh != null) {
                ueh.uncaughtException(t, e);
            else if (!(e instanceof ThreadDeath)) {
                System.err.print("Exception in thread \"" + t.getName() + "\" ");
                e.printStackTrace(System.err);
            }
        }
    }
    正常来说,ThreadGroup会一直递归调用到根ThreadGroup,然后发现parent==null,所以其实没处理,需要注意的是,在实际中,可能会重写ThreadGroup的uncaughtException,这时流程就变了。
  4. ThreadGroup没有处理就交给Thread的defaultUncaughtExceptionHandler处理。
  5. 如果没有defaultUncaughtExceptionHandler,就直接输出异常的名字和打印堆栈信息,也就是一般我们看到的出错信息(一般uncaughtExceptionHandler和defaultUncaughtExceptionHandler没有显式地赋值)。Android中ThreadGroup的uncaughtException方法稍有不同,如下:
    public void uncaughtException(Thread t, Throwable e) {
        // BEGIN android-changed
        if (parent != null) {
            parent.uncaughtException(t, e);
        else if (Thread.getDefaultUncaughtExceptionHandler() != null) {
            // TODO The spec is unclear regarding this. What do we do?
            Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t, e);
        else if (!(e instanceof ThreadDeath)) {
            // No parent group, has to be 'system' Thread Group
            e.printStackTrace(System.err);
        }
        // END android-changed
    }

扩展

统计的代码里使用的是Thread.setDefaultUncaughtExceptionHandler,在崩溃时,将堆栈信息写入数据库,下次启动时发送到服务器。其中

mExceptionHandler.uncaughtException(thread, e);

因为没有显式设置UncaughtExceptionHandler,mExceptionHandler其实是null,相当于又产生了一次空指针异常,可以用try catch捕捉,但是不捕捉也没问题,因为线程的流程走向变化了,运行完uncaughtException中的代码,如果再遇到类似空指针的异常,线程就直接结束了,而这条语句在最后,所以没什么问题,如果插入数据的操作在这条语句之后将不被执行,所以最好还是加个空指针的判断。

要点

  1. 异常会改变了程序的走向,一般性的异常需要使用try catch进行处理,由用户控制程序流程;运行时异常(空指针、除0等)不需要try catch处理,它由虚拟机来控制改变程序流程,一般是调用Thread的uncaughtExceptionHandler或是defaultUncaughtExceptionHandler来处理。
  2. uncaughtExceptionHandler或是defaultUncaughtExceptionHandler异常处理的代码其实也是由发触发异常的线程执行的,执行完毕,线程也就结束了。
  3. 异常的处理顺序为 uncaughtExceptionHandler -> defaultUncaughtExceptionHandler -> 输出堆栈信息(Android的输出内容前面少了Exception in thread "main"之类的东西)。
  4. 在uncaughtException方法中,如果再出现空指针之类的异常,线程就此停止,后续的代码将不再执行。
  5. uncaughtExceptionHandler是对当前线程而言,defaultUncaughtExceptionHandler是对所有线程而言的,一般程序可能会有多个线程,因此一般的做法是使用defaultUncaughtExceptionHandler。

注意

  1. 不管CPU是单核还是多核,操作系统之上是透明的,多任务并发其实是分配时间片。
  2. 并发会造成系统整体性能下降,因为并发控制本身是要耗费资源的。
  3. 一个程序内启用多个线程其实是增加本程序在系统中的比重,让系统分配更多的时间片来执行本程序的代码。
  4. 多线程的同步问题处理不好会造成逻辑混乱,管理线程需要额外的开销,因此使用多线程要慎重,多方面权衡。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值