引言
一般程序崩溃时会退出,同时在控制台输出崩溃时的堆栈信息,如下代码:
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接口注释说明如下:
/**
*
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有两个变量:
private
volatile
UncaughtExceptionHandler
uncaughtExceptionHandler;
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进行处理。
程序崩溃流程如下:
系统分发崩溃消息
/**
*
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,则由它处理,如果没有,则由当前线程的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;
}
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,这时流程就变了。 ThreadGroup没有处理就交给Thread的defaultUncaughtExceptionHandler处理。 如果没有defaultUncaughtExceptionHandler,就直接输出异常的名字和打印堆栈信息,也就是一般我们看到的出错信息(一般uncaughtExceptionHandler和defaultUncaughtExceptionHandler没有显式地赋值)。Android中ThreadGroup的uncaughtException方法稍有不同,如下:
public
void
uncaughtException(Thread
t, Throwable e) {
if
(parent
!=
null
)
{
parent.uncaughtException(t,
e);
}
else
if
(Thread.getDefaultUncaughtExceptionHandler()
!=
null
)
{
Thread.getDefaultUncaughtExceptionHandler().uncaughtException(t,
e);
}
else
if
(!(e
instanceof
ThreadDeath))
{
e.printStackTrace(System.err);
}
}
扩展
统计的代码里使用的是Thread.setDefaultUncaughtExceptionHandler,在崩溃时,将堆栈信息写入数据库,下次启动时发送到服务器。其中
mExceptionHandler.uncaughtException(thread,
e);
因为没有显式设置UncaughtExceptionHandler,mExceptionHandler其实是null,相当于又产生了一次空指针异常,可以用try catch捕捉,但是不捕捉也没问题,因为线程的流程走向变化了,运行完uncaughtException中的代码,如果再遇到类似空指针的异常,线程就直接结束了,而这条语句在最后,所以没什么问题,如果插入数据的操作在这条语句之后将不被执行,所以最好还是加个空指针的判断。
要点
异常会改变了程序的走向,一般性的异常需要使用try catch进行处理,由用户控制程序流程;运行时异常(空指针、除0等)不需要try catch处理,它由虚拟机来控制改变程序流程,一般是调用Thread的uncaughtExceptionHandler或是defaultUncaughtExceptionHandler来处理。 uncaughtExceptionHandler或是defaultUncaughtExceptionHandler异常处理的代码其实也是由发触发异常的线程执行的,执行完毕,线程也就结束了。 异常的处理顺序为 uncaughtExceptionHandler -> defaultUncaughtExceptionHandler -> 输出堆栈信息(Android的输出内容前面少了Exception in thread "main"之类的东西)。 在uncaughtException方法中,如果再出现空指针之类的异常,线程就此停止,后续的代码将不再执行。 uncaughtExceptionHandler是对当前线程而言,defaultUncaughtExceptionHandler是对所有线程而言的,一般程序可能会有多个线程,因此一般的做法是使用defaultUncaughtExceptionHandler。
注意
不管CPU是单核还是多核,操作系统之上是透明的,多任务并发其实是分配时间片。 并发会造成系统整体性能下降,因为并发控制本身是要耗费资源的。 一个程序内启用多个线程其实是增加本程序在系统中的比重,让系统分配更多的时间片来执行本程序的代码。 多线程的同步问题处理不好会造成逻辑混乱,管理线程需要额外的开销,因此使用多线程要慎重,多方面权衡。