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

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)支持传入栈获取信息参数设置。

发布了296 篇原创文章 · 获赞 178 · 访问量 124万+

猜你喜欢

转载自blog.csdn.net/doctor_who2004/article/details/102712891