java9新特性-Stack Walking-当前线程栈信息

本文深入探讨了Java9中StackWalker类的引入及其优势,对比了Java9前后的栈信息获取方式,详细解释了StackWalker如何提高效率并提供更灵活的栈帧控制。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

java语言是基于栈的设计语言,其执行的本质与c、c++语言一样,程序的运行都是一系列进栈出栈操作。JVM中的每个线程启动时都有一个私有的JVM线程栈会创建。栈这种数据结构就是我们常谈到的数据结构中的栈-后进先出的数据结构。栈保存了一系列栈帧,每当一个方法执行时都会伴随着新的栈帧的创建并进栈顶,方法执行完也都会伴随着对应的栈帧的销毁-出栈操作。有关具体细节可以参考https://docs.oracle.com/javase/specs/jvms/se8/html/index.html

在异常日志中我们经常会看到类似:

 

其实打印的都是当前线程一系列栈中保存的代码信息,信息的输出的内容由栈顶到栈尾依次列出。

在java9之前我们一般这样获取栈信息:

public class TypeInference {
    public static void main(String[] args) throws IOException {
        test();
    }

    private static void test() {
        new Throwable().printStackTrace();
    }
}

或者:

 StackTraceElement[] stackTrace = Thread.currentThread().getStackTrace();
        Stream.of(stackTrace).forEachOrdered(System.out::println);
public class TypeInference {
    public static void main(String[] args) throws IOException {
        test();
    }

    private static void test() {
        StackTraceElement[] stackTrace = new Throwable().getStackTrace();
        Stream.of(stackTrace).forEachOrdered(System.out::println);
    }
}
/**
 * Provides programmatic access to the stack trace information printed by
 * {@link #printStackTrace()}.  Returns an array of stack trace elements,
 * each representing one stack frame.  The zeroth element of the array
 * (assuming the array's length is non-zero) represents the top of the
 * stack, which is the last method invocation in the sequence.  Typically,
 * this is the point at which this throwable was created and thrown.
 * The last element of the array (assuming the array's length is non-zero)
 * represents the bottom of the stack, which is the first method invocation
 * in the sequence.
 *
 * <p>Some virtual machines may, under some circumstances, omit one
 * or more stack frames from the stack trace.  In the extreme case,
 * a virtual machine that has no stack trace information concerning
 * this throwable is permitted to return a zero-length array from this
 * method.  Generally speaking, the array returned by this method will
 * contain one element for every frame that would be printed by
 * {@code printStackTrace}.  Writes to the returned array do not
 * affect future calls to this method.
 *
 * @return an array of stack trace elements representing the stack trace
 *         pertaining to this throwable.
 * @since  1.4
 */
public StackTraceElement[] getStackTrace() {
    return getOurStackTrace().clone();
}

根据java.lang.Throwable#getStackTrace文档:我们可以获取当前线程栈信息,数组的第一个元素为栈顶栈。

 

其实堆栈信息一般情况下我们不需要打印,异常堆栈信息由日志框架帮我们搞定。

但是在框架中,堆栈的获取还是有些价值的,比如spring boot框架中:

org.springframework.boot.SpringApplication#deduceMainApplicationClass

	private Class<?> deduceMainApplicationClass() {
		try {
			StackTraceElement[] stackTrace = new RuntimeException().getStackTrace();
			for (StackTraceElement stackTraceElement : stackTrace) {
				if ("main".equals(stackTraceElement.getMethodName())) {
					return Class.forName(stackTraceElement.getClassName());
				}
			}
		}
		catch (ClassNotFoundException ex) {
			// Swallow and continue
		}
		return null;
	}

上面代码利用栈信息寻找我们的main方法所在的spring boot启动类。

 

java9之前的这种获取栈信息有几个缺点:

  • 效率低。java.lang.Throwable#getStackTrace会得到整个栈的信息快照。没办法控制只获取栈顶几个栈帧的信息。
  • 栈帧包含的是方法的名字和类的名字,只是字符串形式,而非class<?>引用.
  • JVM规范运行虚拟机实现的时候为了性能可以丢弃一些栈帧。
  • 没办法针对类信息实现过滤

 

java9对这些缺点做了改进,引入了效率比较好的java.lang.StackWalker类,在获取栈帧的时候可以是懒汉式的。而且StackWalker是线程安全的。

StackWalker instance = StackWalker.getInstance();
        instance.forEach(System.out::println);

或者:

StackWalker instance = StackWalker.getInstance();
        instance.walk(s -> {
            s.forEach(System.out::println);
            return null;
        });

这种walk方法需要传递stream,而且是饿汉式操作的,我们可以过滤、获取栈个数等等流所支持的操作。

而且java.lang.StackWalker#getInstance(java.util.Set<java.lang.StackWalker.Option>, int)支持传入栈获取信息参数设置。

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Dreamer who

你的鼓励将是我创作的最大动力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值