如何在Java 8中获取最后几个堆栈帧而不是完整的堆栈跟踪?

问题描述

Java 9中提供了Stack Walking API,该API使我们仅能获取最后两个堆栈帧。但是我对Java 8有所了解。在Java 8中,我们可以使用Thread.currentThread().getStackTrace()new Throwable().getStackTrace()获取堆栈跟踪,但是这两种方法都很慢,因为它们构造了整个堆栈跟踪。有什么方法可以限制堆栈跟踪中的帧数(例如仅构造最后3帧)?

更新: 我测试了Stephen的代码,它确实运行良好。根据选择的深度,它确实将执行时间减少了一个恒定因子。我发现有趣的是,即使堆栈跟踪是在异常创建期间构造并以内部格式存储的,它也不会减少创建新异常的时间。调用StackTraceElement时,大部分时间都花在将内部格式转换为getStackTrace()上。因此,如果堆栈跟踪较浅,则转换它所需的时间当然会更少。但是,为什么在创建异常(new Throwable())时创建堆栈跟踪的内部表示却花更少的时间呢?


    public static void main(String[] args) throws IOException {
        f1();
    }

    private static void f1() {
       f2();
    }

    private static void f2() {
       f3();
    }

    private static void f3() {
       f4();
    }

    private static void f4() {
        f5();
    }

    private static void f5() {
        int sum = 0;
        long l = System.currentTimeMillis();
        for (int i = 0; i < 100000; i++) {
            Throwable throwable = new Throwable();
            //comment out to test the internal representation -> StackTraceElement conversion
            //StackTraceElement[] trace = throwable.getStackTrace();
            //sum += trace.length;
        }
        long l2 = System.currentTimeMillis();
        System.out.println(l2-l);
        System.out.println(sum);
    }

更新2:为消除Benchamrk问题,我将所有Throwable实例存储在一个数组中,然后在执行结束时随机检索其中一个实例并打印堆栈跟踪。我认为这将阻止为消除new Throwable()调用而进行的任何优化。

        int sizez = 2000000;
        Throwable[] th = new Throwable[sizez];
        for (int i = 0; i < sizez; i++) {
            th[i] = new Throwable();
            //StackTraceElement[] trace = throwable.getStackTrace();
            //sum += trace.length;
        }
        long l2 = System.currentTimeMillis();
        System.out.println(l2-l);
        System.out.println(sum);

        Random rand = new Random();
        StackTraceElement[] trace = th[rand.nextInt(sizez)].getStackTrace();
        System.out.println(trace[0]);

解决方法

您可以使用JVM选项-XX:MaxJavaStackTraceDepth=depth来限制堆栈跟踪的大小。

但是,这适用于所有堆栈跟踪,听起来并不像您所需要的那样。我认为没有一种方法可以根据具体情况来限制大小。