前言
繼之前的文章詳解jvm如何處理異常,今天再次發布一篇比較關聯的文章,如題目可知,今天聊一聊在jvm中線程遇到未捕獲異常的問題,其中涉及到線程如何處理未捕獲異常和一些內容介紹。
什么是未捕獲異常
未捕獲異常指的是我們在方法體中沒有使用try-catch捕獲的異常,比如下面的例子
1
2
3
4
5
6
7
|
private static void testuncaughtexception(string arg) { try { system.out.println( 1 / arg.length()); } catch (arithmeticexception e) { e.printstacktrace(); } } |
上面的代碼很有可能發生如下情況
- 如果方法參數arg傳遞null,會出現nullpointerexception
- 如果參數arg傳遞內容為空的字符串(“”),會出現arithmeticexception
對于上面的問題,我們不難發現
- 上面可能出現的nullpointerexception和arithmeticexception都屬于unchecked exceptions
- 而arithmeticexception被我們人為try-catch捕獲了,它不符合本文對于未捕獲異常的定義
- nullpointerexception 由于我們沒有catch住,就變成了我們要聊的未捕獲異常
- 另外,未捕獲異常實際是unchecked exceptions的子集
uncaughtexceptionhandler 是什么
- 它是線程遇到未捕獲異常的一個處理者接口
-
它包含一個方法
void uncaughtexception(thread t, throwable e);
用來處理接收處理異常發生后的操作,比如收集崩潰信息并上報等 -
可以通過 實例方法
thread.setuncaughtexceptionhandler
為某一個thread實例設置未捕獲異常處理者 -
也可以通過 靜態方法
thread.setdefaultuncaughtexceptionhandler
設置所有thread實例的未捕獲異常處理者
threadgroup 是什么
- threadgroup 是線程的集合
- threadgroup 也可以包含子threadgroup
- 除了初始的threadgroup 之外,每個threadgroup都有一個父 threadgroup
- threadgroup 自身實現了thread.uncaughtexceptionhandler,用來相應處理其內部的線程和threadgroup發生未捕獲異常。
未捕獲異常處理者 設置指南
線程發生了未捕獲異常,jvm怎么處理
分發throwable實例
當線程a中出現了未捕獲異常時,jvm會調用線程a的dispatchuncaughtexception(throwable)
方法
1
2
3
4
5
6
7
|
/** * 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); } |
獲取未捕獲異常處理者
每個線程會有一個變量(uncaughtexceptionhandler)來保存未捕獲異常的處理者
在線程需要確定throwable分發目標的處理者時,優先獲取當前線程中uncaughtexceptionhandler變量
如果出問題線程的uncaughtexceptionhandler為null(即沒有顯式設置異常處理者),則使用自己所在的threadgroup來作為未捕獲異常處理者。
1
2
3
4
5
6
7
8
9
10
11
12
13
|
/** * 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 * @return the uncaught exception handler for this thread */ public uncaughtexceptionhandler getuncaughtexceptionhandler() { return uncaughtexceptionhandler != null ? uncaughtexceptionhandler : group; } |
如果throwable分發給threadgroup
- threadgroup會嘗試轉給它的父threadgroup(如果存在的話)
- 如果上面沒有找到對應的threadgroup,則嘗試獲取thread.getdefaultuncaughtexceptionhandler()并分發
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
|
/** * called by the java virtual machine when a thread in this * thread group stops because of an uncaught exception, and the thread * does not have a specific {@link thread.uncaughtexceptionhandler} * installed. * <p> * the <code>uncaughtexception</code> method of * <code>threadgroup</code> does the following: * <ul> * <li>if this thread group has a parent thread group, the * <code>uncaughtexception</code> method of that parent is called * with the same two arguments. * <li>otherwise, this method checks to see if there is a * {@linkplain thread#getdefaultuncaughtexceptionhandler default * uncaught exception handler} installed, and if so, its * <code>uncaughtexception</code> method is called with the same * two arguments. * <li>otherwise, this method determines if the <code>throwable</code> * argument is an instance of {@link threaddeath}. if so, nothing * special is done. otherwise, a message containing the * thread's name, as returned from the thread's {@link * thread#getname getname} method, and a stack backtrace, * using the <code>throwable</code>'s {@link * throwable#printstacktrace printstacktrace} method, is * printed to the {@linkplain system#err standard error stream}. * </ul> * <p> * applications can override this method in subclasses of * <code>threadgroup</code> to provide alternative handling of * uncaught exceptions. * * @param t the thread that is about to exit. * @param e the uncaught exception. * @since jdk1.0 */ 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); } } } |
將上面的處理流程做成圖的形式,就是下圖所示
注:上述圖片來自https://www.javamex.com/tutorials/exceptions/exceptions_uncaught_handler.shtml
questions
初始的threadgroup是什么
上面提到了初始的threadgroup沒有父threadgroup,是主線程所在的threadgroup么?
這個問題,我們可以通過這樣一段代碼驗證
1
2
3
4
5
6
7
|
private static void dumpthreadgroups() { threadgroup threadgroup = thread.currentthread().getthreadgroup(); while (threadgroup != null ) { system.out.println( "dumpthreadgroups threadgroup=" + threadgroup.getname()); threadgroup = threadgroup.getparent(); } } |
執行該方法對應的輸出是
dumpthreadgroups threadgroup=main
dumpthreadgroups threadgroup=system
因此我們可以發現,初始的threadgroup是一個叫做system的threadgroup,而不是main threadgroup
setdefaultuncaughtexceptionhandler 設置的一定會被調用到么
這其實是一個很好的問題,答案是不一定會被調用,因為可能存在以下的情況
- 出問題的線程設置了對應的uncaughtexcpetionhandler,優先響應分發到這個handler
- 出問題的線程所在的threadgroup包括其祖先threadgroup 重寫了uncaughtexception 也可能造成線程默認的handler無法被調用
- 出問題的線程重寫了dispatchuncaughtexception 可能性較小
- 出問題的線程重寫了getuncaughtexceptionhandler 可能性較小
參考聲明
how uncaught exceptions are handled
總結
以上就是這篇文章的全部內容了,希望本文的內容對大家的學習或者工作具有一定的參考學習價值,如果有疑問大家可以留言交流,謝謝大家對服務器之家的支持。
原文鏈接:https://droidyue.com/blog/2019/01/06/how-java-handle-uncaught-exceptions/