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

huangapple go评论82阅读模式
英文:

How to get last few stack frames instead of a complete stack trace in Java 8?

问题

在Java 9中有Stack Walking 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();
            //注释以测试内部表示 -> StackTraceElement的转换
            //StackTraceElement[] trace = throwable.getStackTrace();
            //sum += trace.length;
        }
        long l2 = System.currentTimeMillis();
        System.out.println(l2-l);
        System.out.println(sum);
    }

更新2:为了消除基准问题,我将所有的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]);
英文:

There is the Stack Walking API in Java 9 which allows us to get only the last couple of stack frames. But I am stuck with Java 8. In Java 8 we can get the Stack trace with either Thread.currentThread().getStackTrace() or new Throwable().getStackTrace() but both of these methods are slow because they construct the whole stack trace. Is there any way to limit the number of frames in the stack trace (to construct only the last 3 frames for example)?

Update:
I tested Stephen's code and it works really well. It really reduces execution time by a constant factor depending on the the depth chosen. What I found interesting is that it does not reduce the time to create a new Exception even though the stack trace is constructed during exception creation and stored in an internal format. Most of the time is spent converting this internal format to a StackTraceElement when getStackTrace() is called. So if the stack trace is shallower, than of course it takes less time to convert it. But why doesn't it take less time to create the internal representation of the stack trace when the exception is created(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);
    }

Update 2: Trying to eliminate benchamrk issues, I stored all my Throwable instances in an array then at the end of execution I retrieved one of them by random and printed the stack trace. I think this would prevent any optimizations done to eliminate the new Throwable() calls.

        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]);

答案1

得分: 3

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

然而,这适用于所有的堆栈跟踪,听起来并不像是你所需要的。我认为没有办法根据情况逐案限制大小。

英文:

You can use the JVM option -XX:MaxJavaStackTraceDepth=depth to limit the size of stacktraces.

However, this applies to all stacktraces, and it doesn't sound like it is what you need. I don't think there is a way to limit the size on a case by case basis.

huangapple
  • 本文由 发表于 2020年9月5日 19:02:34
  • 转载请务必保留本文链接:https://go.coder-hub.com/63753190.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定