天天看點

捕獲線程異常與Hook線程

捕獲線程運作時異常API

void setUncaughtExceptionHandler(UncaughtExceptionHandler eh)

為某個線程指定uncaughtExceptionHandler

static void setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler eh)

設定全局的uncaughtExceptionHandler

static UncaughtExceptionHandler getDefaultUncaughtExceptionHandler()

擷取全局的uncaughtExceptionHandler

UncaughtExceptionHandler getUncaughtExceptionHandler()

擷取特定線程的uncaughtExceptionHandler

UncaughtExceptionHandler介紹

線程在執行單元中是不允許抛出checked異常的,線上程的運作上下文中,派生它的線程無法直接擷取它運作中得異常資訊。對此java提供了UncaughtExceptionHandler接口,當線程運作過程中出現異常時會回調UncaughtExceptionHandler接口,進而得知是哪個線程運作時出了什麼樣的錯。

@FunctionalInterface
    public interface UncaughtExceptionHandler {
  
        void uncaughtException(Thread t, Throwable e);
    }
    
	 private void dispatchUncaughtException(Throwable e) {
        getUncaughtExceptionHandler().uncaughtException(this, e);
    }
           

UncaughtExceptionHandler 是一個FunctionalInterface,隻有一個抽象方法,當線程運作過程中出現異常時,JVM會調用dispatchUncaughtException将對應線程的異常資訊傳遞給回調接口。

getUncaughtExceptionHandler源碼

public UncaughtExceptionHandler getUncaughtExceptionHandler() {
        return uncaughtExceptionHandler != null ?
            uncaughtExceptionHandler : group;
    }
           

方法首先會判斷目前線程是否設定了uncaughtExceptionHandler ,有則執行線程自己的uncaughtException(),否則就到ThreadGroup中擷取。

ThreadGroup 的uncaughtException源碼

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則直接調用父Group的uncaughtException(),如果設定了全局預設的UncaughtExceptionHandler,則調用uncaughtException(),既沒有設定全局UncaughtExceptionHandler也沒有父ThreadGroup則直接将異常資訊定向到System.err中。

UncaughtExceptionHandler 測試代碼

public static void main(String[] args) {
		Thread.setDefaultUncaughtExceptionHandler((t,e)->{
			System.out.println(t.currentThread().getName()+"抛出異常  " +e.getMessage());
			e.printStackTrace();
		});
		new Thread(()->{
			System.out.println(1/0);
		},"異常線程1").start();
	}
           

輸出

異常線程1抛出異常 / by zero

java.lang.ArithmeticException: / by zero

at ThreadException.ExceptionTest.lambda$1(ExceptionTest.java:11)

at java.lang.Thread.run(Unknown Source)

Hook線程介紹(鈎子線程)

JVM程序的退出是由于沒有活躍的非守護線程,或者收到了系統中斷信号,向JVM程式注入一個Hook線程,在JVM程序退出的時候,Hook線程才會啟動執行。可以通過Runtime向JVM注入多個Hook線程。

hook線程注入例子

public static void main(String[] args) {
		Runtime.getRuntime()
				.addShutdownHook(new Thread(()->{
					System.out.println("hook test 1 running");
					try {
						TimeUnit.SECONDS.sleep(1);
					} catch (InterruptedException e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
					System.out.println("hook test 1 exit");
				},"HOOK線程1"));
		
		Runtime.getRuntime()
		.addShutdownHook(new Thread(()->{
			System.out.println("hook test 2 running");
			try {
				TimeUnit.SECONDS.sleep(1);
			} catch (InterruptedException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
			System.out.println("hook test 2 exit");
		},"HOOK線程2"));
		
		System.out.println("main thread  exit");
	}
           

輸出

main thread exit

hook test 2 running

hook test 1 running

hook test 1 exit

hook test 2 exit

Hook線程注意事項及使用場景:

  1. Hook線程隻有在收到JVM程序退出信号的時候才會被執行。
  2. Hook線程可以執行一些資源釋放工作,比如關閉檔案句柄,socket連結等。
  3. 盡量不要在Hook線程中執行耗時長的操作,會導緻程式遲遲不能退出。