Java异常的栈轨迹(Stack
Trace)

捕获到异常时,往往需要进行一些处理。比较简单直接的方式就是打印异常栈轨迹Stack Trace。说起栈轨迹,可能很多人和我一样,第一反应就是printStackTrace()方法。其实除了这个方法,还有一些别的内容也是和栈轨迹有关的。

1.printStackTrace()

首先需要明确,这个方法并不是来自于Exception类。Exception类本身除了定义了几个构造器之外,所有的方法都是从其父类继承过来的。而和异常相关的方法都是从java.lang.Throwable类继承过来的。而printStackTrace()就是其中一个。

这个方法会将Throwable对象的栈轨迹信息打印到标准错误输出流上。输出的大体样子如下:

1
2
3
4
java.lang.NullPointerException
         at
MyClass.mash(MyClass.java:
9)
         at
MyClass.crunch(MyClass.java:
6)
         at
MyClass.main(MyClass.java:
3)

输出的第一行是toString()方法的输出,后面几行的内容都是之前通过fillInStackTrace()方法保存的内容。关于这个方法,我们后面会讲。

下面看一个例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public class TestPrintStackTrace
{
    public static void f()
throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g()
throws Exception{
        f();
    }
    public static void main(String[]
args) {
        try {
            g();
        }catch(Exception
e) {
            e.printStackTrace();
        }
    }
}

这个例子的输出如下:

1
2
3
4
java.lang.Exception:
出问题啦!
    at
TestPrintStackTrace.f(TestPrintStackTrace.java:
3)
    at
TestPrintStackTrace.g(TestPrintStackTrace.java:
6)
    at
TestPrintStackTrace.main(TestPrintStackTrace.java:
10)

在这个例子中,在方法f()中抛出异常,方法g()中调用方法f(),在main方法中捕获异常,并且打印栈轨迹信息。因此,输出依次展示了f—>g—>main的过程。

2.getStackTrace()方法

这个方法提供了对printStackTrace()方法所打印信息的编程访问。它会返回一个栈轨迹元素的数组。以上面的输出为例,输出的第2-4行每一行的内容对应一个栈轨迹元素。将这些栈轨迹元素保存在一个数组中。每个元素对应栈的一个栈帧。数组的第一个元素保存的是栈顶元素,也就是上面的f。最后一个元素保存的栈底元素。

下面是一个使用getStackTrace()访问这些轨迹栈元素并打印输出的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public class TestPrintStackTrace
{
    public static void f()
throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g()
throws Exception{
        f();
    }
    public static void main(String[]
args) {
        try {
            g();
        }catch(Exception
e) {
            e.printStackTrace();
            System.out.println("------------------------------");
            for(StackTraceElement
elem : e.getStackTrace()) {
                System.out.println(elem);
            }
        }
    }
}

这样的输出和printStackTrace()的输出基本上是一样的,如下:

1
2
3
4
5
6
7
java.lang.Exception:
出问题啦!
    at
TestPrintStackTrace.f(TestPrintStackTrace.java:
3)
    at
TestPrintStackTrace.g(TestPrintStackTrace.java:
6)
    at
TestPrintStackTrace.main(TestPrintStackTrace.java:
10)
TestPrintStackTrace.f(TestPrintStackTrace.java:3)
TestPrintStackTrace.g(TestPrintStackTrace.java:6)
TestPrintStackTrace.main(TestPrintStackTrace.java:10)

3.fillInStackTrace()

我们在前面也提到了这个方法。要说清楚这个方法,首先要讲一下捕获异常之后重新抛出的问题。在catch代码块中捕获到异常,打印栈轨迹,又重新throw出去。在上一级的方法调用中,再捕获这个异常并且打印出栈轨迹信息。这两个栈轨迹信息会一样吗?我们看一下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
public class TestPrintStackTrace
{
    public static void f()
throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g()
throws Exception{
        try {
            f();
        }catch(Exception
e) {
            e.printStackTrace();
            throw e;
        }
         
    }
    public static void main(String[]
args) {
        try {
            g();
        }catch(Exception
e) {
            e.printStackTrace();
        }
    }
}

在main方法中捕获的异常,是在g()方法中抛出的,按理说这两个打印栈轨迹的信息应该不同,第二次打印的信息应该没有关于f的信息。但是事实上,两次打印栈轨迹信息是一样的。输出结果如下:

1
2
3
4
5
6
7
8
java.lang.Exception:
出问题啦!
    at
TestPrintStackTrace.f(TestPrintStackTrace.java:
3)
    at
TestPrintStackTrace.g(TestPrintStackTrace.java:
7)
    at
TestPrintStackTrace.main(TestPrintStackTrace.java:
16)
java.lang.Exception:
出问题啦!
    at
TestPrintStackTrace.f(TestPrintStackTrace.java:
3)
    at
TestPrintStackTrace.g(TestPrintStackTrace.java:
7)
    at
TestPrintStackTrace.main(TestPrintStackTrace.java:
16)

也就是说,捕获到异常又立即抛出,在上级方法调用中再次捕获这个异常,打印的栈轨迹信息是一样的。原因在于没有将当前线程当前状态下的轨迹栈的状态保存进Throwabe中。现在我们引入fillInStackTrace()方法。这个方法刚好做的就是这样的保存工作。我们看一下这个方法的原型:

1
public Throwable
fillInStackTrace()

这个方法是有返回值的。返回的是保存了当前栈轨迹信息的Throwable对象。我们看看使用fillInStackTrace()方法处理后,打印的栈轨迹信息有什么不同,代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class TestPrintStackTrace
{
    public static void f()
throws Exception{
        throw new Exception("出问题啦!");
    }
    public static void g()
throws Exception{
        try {
            f();
        }catch(Exception
e) {
            e.printStackTrace();
            //不要忘了强制类型转换
            throw (Exception)e.fillInStackTrace();
        }
         
    }
    public static void main(String[]
args) {
        try {
            g();
        }catch(Exception
e) {
            e.printStackTrace();
        }
    }
}

输出如下:

1
2
3
4
5
6
7
java.lang.Exception:
出问题啦!
    at
TestPrintStackTrace.f(TestPrintStackTrace.java:
3)
    at
TestPrintStackTrace.g(TestPrintStackTrace.java:
7)
    at
TestPrintStackTrace.main(TestPrintStackTrace.java:
17)
java.lang.Exception:
出问题啦!
    at
TestPrintStackTrace.g(TestPrintStackTrace.java:
11)
    at
TestPrintStackTrace.main(TestPrintStackTrace.java:
17)

我们看到,在main方法中打印栈轨迹已经没有了f相关的信息了。

以上就是关于Java栈轨迹的一些我之前没有掌握的内容,记下来备忘。

java异常分析;剖析printStackTrace和fillInStackTrace的更多相关文章

  1. Java 异常分析

    Java 异常分析 本文是对以下内容的分析: Java异常设计 Java 异常分类 Java异常可以告诉什么问题 Java异常处理最佳实践 Java Exception 是为了处理应用程序的异常行为而 ...

  2. 【55】java异常机制剖析

    一.为什么要使用异常 首先我们可以明确一点就是异常的处理机制可以确保我们程序的健壮性,提高系统可用率.虽然我们不是特别喜欢看到它,但是我们不能不承认它的地位,作用.有异常就说明程序存在问题,有助于我们 ...

  3. java异常相关说明(printStackTrace,fillInStackTrace等)

    我们在实际场景中很容易catch(Exception e) 简单粗暴 这样写代码有几个问题 1.你无法细分具体异常 因为有时需要针对不同异常 产生不同的应对行为 2.直接exception 往往不会包 ...

  4. Java异常的栈轨迹fillInStackTrace和printStackTrace的用法

    本文转自wawlian 捕获到异常时,往往需要进行一些处理.比较简单直接的方式就是打印异常栈轨迹Stack Trace.说起栈轨迹,可能很多人和我一样,第一反应就是printStackTrace()方 ...

  5. 基础知识《十四》Java异常的栈轨迹fillInStackTrace和printStackTrace的用法

    本文转自wawlian 捕获到异常时,往往需要进行一些处理.比较简单直接的方式就是打印异常栈轨迹Stack Trace.说起栈轨迹,可能很多人和我一样,第一反应就是printStackTrace()方 ...

  6. JAVA异常处理原则和log4j输出详细异常分析

    1.多用try,catch;不要一个try,catch包含所有内容 好处:不同模块抓取不同异常,某一模块异常挂了,不影响其他模块的程序的进行 2.多写几个catche:尽量不要使用Exception这 ...

  7. java.net.SocketException:Software caused connection abort: recv failed 异常分析 +socket客户端&服务端代码

    java.net.SocketException:Software caused connection abort: recv failed 异常分析 分类: 很多的技术 2012-01-04 12: ...

  8. Java ConcurrentModificationException 异常分析与解决方案

    Java ConcurrentModificationException 异常分析与解决方案http://www.2cto.com/kf/201403/286536.html java.util.Co ...

  9. Java 异常分类

    1.java异常层次结构: 粉红色的是已检查异常(checked exceptions),其必须被 try{}catch语句块所捕获,或者在方法签名里通过throws子句声明.已检查异常必须在编译时被 ...

随机推荐

  1. Kali桥接模式下配置动态ip

    以管理员身份运行虚拟机 打开 控制面板-->网络和Internet-->更改适配器 在虚拟机处桥接到这个WLAN2 点击 编辑-->编辑虚拟网卡 没有网卡就点上图的添加网络作为桥接网 ...

  2. inotifywait命令详解及安装

    https://www.cnblogs.com/pyrene/p/6414724.html     安装 https://www.cnblogs.com/martinzhang/p/4126907.h ...

  3. 20.docker 持久化存储与数据共享

    1.image layer 和 container layer 的关系 image layer 是可读的 container layer 是在image layer 之上创建的 一个可读可写层 con ...

  4. jquery 第二节 Dom和jQuery的互相转换

    1.Dom转jQuery Dom对象: var v_dom = document.getElementById("name"); 转换: jQuery对象:    var v_jq ...

  5. CEO、COO、CFO、CTO、CIO是什么意思/?

    CEO Chief Executive Officer 首席执行官 COO Chief Operating Officer 首席运营官 CFO Chief Financial Officer 首席财务 ...

  6. 题解 P2016 【战略游戏】

    题目 解法跟 dalao @real_ljs 类似,但没有用到递归 [分析] 题目相当于需要求覆盖这颗树需要的最小点数 用 \(Dp_{i,0/1}\) 表示在这棵树中,以 \(i\) 为根节点的子树 ...

  7. PAT Basic 1017 A除以B (20) [数学问题-⼤整数运算]

    题目 本题要求计算A/B,其中A是不超过1000位的正整数,B是1位正整数.你需要输出商数Q和余数R,使得A = B * Q + R成⽴. 输⼊格式: 输⼊在1⾏中依次给出A和B,中间以1空格分隔. ...

  8. react-native屏幕适配

    写一个屏幕适配类文件AdapterUtil.js,这样避免每次进行单位换算 "use strict" import {Dimensions, StatusBar, Platform ...

  9. 题解 P1884 【[USACO12FEB]过度种植(银)Overplanting 】

    什么,扫描线需要线段树? 那我第一个不干啊(其实是不会写) 这里介绍一种裸的扫描线: 我们根据x排序,对于相等的 \(x\) ,将 \(y\) 进入和退出分类讨论,然后全部放进set里面.每次 \(x ...

  10. [NOI2014]魔法森林(LCT)

    蒟蒻又发现自己还没写过LCT…… 首先显然按照权值a从小到大排序,维护b的最小生成树.然后直接扫,代价是加入b的最大值,然后动态加边,因为有边权,所以在lct中边应该理解为点.每次连接(u,v)时,若 ...