本文中,将介绍如何获取线程在运行时期的异常信息,以及如何向 Java 程序注入 Hook 的过程
UncaughtExceptionHandler 介绍
线程在执行单元中是不允许抛出 checked 异常的,(run 方法签名中不能抛出异常)而且线程运行在自己的上下文中,派生它的线程无法直接获得它在运行出现的异常信息,对此,Java为我们提供UncaughtExceptionHandler 接口,当线程在运行过程中出现异常时,回调 UncaughtExceptionHandler 接口,从而得知是哪个线程在运行时出错,以及出现什么错误。
public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { int b = 10 / 0; try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(b); }); t1.setUncaughtExceptionHandler((thread, e) ->{ System.out.println(thread); System.out.println(e); }); }UncaughtExceptionHandler 源码分析
在没有向线程注入 UncaughtExceptionHandler 回调接口的情况下,线程若出现异常将如何处理了?
public UncaughtExceptionHandler getUncaughtExceptionHandler() { return uncaughtExceptionHandler != null ? uncaughtExceptionHandler : group; }getUncaughtExceptionHandler 方法首先判断当前线程是否设置了 handler ,如果有则执行自己uncaughtExceptionHandler 方法,否则就到 ThreadGroup 中获取
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, 则直接调用父 Group 的 uncaughtException 方法如果设置了全局默认的 UncaughtExceptionHandler ,则调用 uncaughtException 方法否则 直接将异常的堆栈信息定向到System.err 中Hook 线程介绍
JVM 进程的退出是由于 JVM 进程中没有活跃的非守护线程,或者收到系统中中断信号,想 JVM 程序注入一个 Hook 线程,在 JVM 退出的时候,Hook 线程会启动执行,通过 Runtime 可以为 JVM 注入多个 Hook 线程,下面通过一个简单例子来看看
public class ThreadHook { public static void main(String[] args) { Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run(){ System.out.println("The hook thread 1 is running "); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("The hook thread 1 will exit "); } }); Runtime.getRuntime().addShutdownHook(new Thread(){ @Override public void run(){ System.out.println("The hook thread 2 is running "); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("The hook thread 2 will exit "); } }); System.out.println("The program will is stopping "); } }代码中,给 Java 注入两个 Hook 线程,在main线程结束,JVM 即将退出时,两个 Hook 线程会被启动且运行
Hook 线程应用场景 以及 注意事项
Hook 线程只有在收到退出信号的时候被执行,如果 使用 kiil -9 强制杀死进程的方式,Hook 线程不会得到执行Hook 线程也可以执行一些资源释放的工作,比如关闭文件句柄,socket 链接,数据库 connection 等尽量不要在 Hook 执行耗时操作,会导致程序迟迟不能退出