Tips
做一个终身学习的人。
在本章中,主要介绍以下内容:
JVM 中的每个线程都有一个私有的 JVM 栈,它在创建线程的同时创建。 该栈是先进先出(LIFO)数据结构。 栈保存栈帧。 每次调用一个方法时,都会创建一个新的栈帧并将其推送到栈的顶部。 当方法调用完成时,栈帧销毁(从栈中弹出)。 堆栈中的每个栈帧都包含自己的局部变量数组,以及它自己的操作数栈,返回值和对当前方法类的运行时常量池的引用。 JVM 的具体实现可以扩展一个栈帧来保存更多的信息。
JVM 栈上的一个栈帧表示给定线程中的 Java 方法调用。 在给定的线程中,任何点只有一个栈帧是活动的。 活动栈帧被称为当前栈帧,其方法称为当前方法。 定义当前方法的类称为当前类。 当方法调用另一种方法时,栈帧不再是当前栈帧 —— 新的栈帧被推送到栈,并且执行方法成为当前方法,并且新栈帧成为当前栈帧。 当方法返回时,旧栈帧再次成为当前帧。 有关 JVM 栈和栈帧的更多详细信息,请参阅 https://docs.oracle.com/javase/specs/jvms/se8/html/index.html 上的 Java 虚拟机规范。
Tips
如果 JVM 支持本地方法,则线程还包含本地方法栈,该栈包含每个本地方法调用的本地方法栈帧。
下图显示了两个线程及其 JVM 栈。 第一个线程的 JVM 栈包含四个栈帧,第二个线程的 JVM 栈包含三个栈帧。 Frame 4 是 Thread-1 中的活动栈帧,Frame 3 是 Thread-2 中的活动栈帧。
虚拟机栈遍历是遍历线程的栈帧并检查栈帧的内容的过程。 从 Java 1.4 开始,可以获取线程栈的快照,并获取每个栈帧的详细信息,例如方法调用发生的类名称和方法名称,源文件名,源文件中的行号等。 栈遍历中使用的类和接口位于 Stack-Walking API 中。
在 JDK 9 之前,可以使用 java.lang 包中的以下类遍历线程栈中的所有栈帧:
类的实例表示栈帧。
- StackTraceElement
类的
- Throwable
方法返回一含当前线程栈的栈帧的
- getStackTrace()
数组。
- StackTraceElement []
类的
- Thread
方法返回一个
- getStackTrace()
数组,它包含线程栈的栈帧。 数组的第一个元素是栈中的顶层栈帧,表示序列中最后一个方法调用。 JVM 的一些实现可能会在返回的数组中省略一些栈帧。
- StackTraceElement []
类包含以下方法,它返回由栈帧表示的方法调用的详细信息:
- StackTraceElement
- String getClassLoaderName() String getClassName() String getFileName() int getLineNumber() String getMethodName() String getModuleName() String getModuleVersion() boolean isNativeMethod()
Tips
在 JDK 9 中将,
- getModuleName()
和
- getModuleVersion()
方法添加到此类中。
- getClassLoaderName()
类中的大多数方法都有直观的名称,例如,
- StackTraceElement
方法返回调用由此栈帧表示的方法的名称。
- getMethodName()
方法返回包含方法调用代码的源文件的名称,
- getFileName()
返回源文件中的方法调用代码的行号。
- getLineNumber()
以下代码片段显示了如何使用
和
- Throwable
类检查当前线程的栈:
- Thread
- // Using the Throwable class
- StackTraceElement[] frames = new Throwable().getStackTrace();
- // Using the Thread class
- StackTraceElement[] frames2 = Thread.currentThread().getStackTrace();
- // Process the frames here...
本章中的所有程序都是 com.jdojo.stackwalker 模块的一部分,其声明如下所示。
- // module-info.java
- module com.jdojo.stackwalker {
- exports com.jdojo.stackwalker;
- }
下面包含一个
类的代码。 该类的输出在 JDK 8 中运行时生成。
- LegacyStackWalk
- // LegacyStackWalk.java
- package com.jdojo.stackwalker;
- import java.lang.reflect.InvocationTargetException;
- public class LegacyStackWalk {
- public static void main(String[] args) {
- m1();
- }
- public static void m1() {
- m2();
- }
- public static void m2() {
- // Call m3() directly
- System.out.println("\nWithout using reflection: ");
- m3();
- // Call m3() using reflection
- try {
- System.out.println("\nUsing reflection: ");
- LegacyStackWalk.class.getMethod("m3").invoke(null);
- } catch(NoSuchMethodException | InvocationTargetException | IllegalAccessException | SecurityException e) {
- e.printStackTrace();
- }
- }
- public static void m3() {
- // Prints the call stack details
- StackTraceElement[] frames = Thread.currentThread().getStackTrace();
- for (StackTraceElement frame: frames) {
- System.out.println(frame.toString());
- }
- }
- }
输出结果:
- java.lang.Thread.getStackTrace(Thread.java: 1552) com.jdojo.stackwalker.LegacyStackWalk.m3(LegacyStackWalk.java: 37) com.jdojo.stackwalker.LegacyStackWalk.m2(LegacyStackWalk.java: 18) com.jdojo.stackwalker.LegacyStackWalk.m1(LegacyStackWalk.java: 12) com.jdojo.stackwalker.LegacyStackWalk.main(LegacyStackWalk.java: 8) Using reflection: java.lang.Thread.getStackTrace(Thread.java: 1552) com.jdojo.stackwalker.LegacyStackWalk.m3(LegacyStackWalk.java: 37) sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62) sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43) java.lang.reflect.Method.invoke(Method.java: 498) com.jdojo.stackwalker.LegacyStackWalk.m2(LegacyStackWalk.java: 25) com.jdojo.stackwalker.LegacyStackWalk.m1(LegacyStackWalk.java: 12) com.jdojo.stackwalker.LegacyStackWalk.main(LegacyStackWalk.java: 8)
类的
- LegacyStackWalk
方法调用
- main()
方法,它调用
- m1()
方法。
- m2()
方法直接调用
- m2()
方法两次,其中一次使用了反射。
- m3()
方法使用
- m3()
类的
- Thread
方法获取当前线程栈快照,并使用
- getStrackTrace()
类的
- StackTraceElement
方法打印栈帧的详细信息。 可以使用此类的方法来获取每个栈帧的相同信息。 当在 JDK 9 中运行
- toString()
类时,输出包括每行开始处的模块名称和模块版本。 JDK 9 的输出如下:
- LegacyStackWalk
- Without using reflection: java.base / java.lang.Thread.getStackTrace(Thread.java: 1654) com.jdojo.stackwalker / com.jdojo.stackwalker.LegacyStackWalk.m3(LegacyStackWalk.java: 37) com.jdojo.stackwalker / com.jdojo.stackwalker.LegacyStackWalk.m2(LegacyStackWalk.java: 18) com.jdojo.stackwalker / com.jdojo.stackwalker.LegacyStackWalk.m1(LegacyStackWalk.java: 12) com.jdojo.stackwalker / com.jdojo.stackwalker.LegacyStackWalk.main(LegacyStackWalk.java: 8) Using reflection: java.base / java.lang.Thread.getStackTrace(Thread.java: 1654) com.jdojo.stackwalker / com.jdojo.stackwalker.LegacyStackWalk.m3(LegacyStackWalk.java: 37) java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) java.base / jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java: 62) java.base / jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java: 43) java.base / java.lang.reflect.Method.invoke(Method.java: 538) com.jdojo.stackwalker / com.jdojo.stackwalker.LegacyStackWalk.m2(LegacyStackWalk.java: 25) com.jdojo.stackwalker / com.jdojo.stackwalker.LegacyStackWalk.m1(LegacyStackWalk.java: 12) com.jdojo.stackwalker / com.jdojo.stackwalker.LegacyStackWalk.main(LegacyStackWalk.java: 8)
在 JDK 9 之前,Stack-Walking API 存在以下缺点:
类的
- Throwable
方法返回整个栈的快照。 没有办法在栈中只得到几个顶部栈帧。
- getStrackTrace()
类的实例,而类名只是字符串。
- Class<?>
类的
- Module
方法,调用者的类必须在同一个模块中。 否则,将抛出一个
- addExports()
异常。 在现有的 API 中,没有简单而有效的方式来获取调用者的类引用。 这样的 API 依赖于使用 JDK 内部 API ——
- IllegalCallerException
类的
- sun.reflect.Reflection
静态方法。
- getCallerClass()
JDK 9 引入了一个新的 Stack-Walking API,它由 java.lang 包中的
类组成。 该类提供简单而有效的栈遍历。 它为当前线程提供了一个顺序的栈帧流。 从栈生成的最上面的到最下面的栈帧,栈帧按顺序记录。
- StackWalker
类非常高效,因为它可以懒加载的方式地评估栈帧。 它还包含一个便捷的方法来获取调用者类的引用。
- StackWalker
类由以下成员组成:
- StackWalker
嵌套枚举
- StackWalker.Option
嵌套接口
- StackWalker.StackFrame
类实例的方法
- StackWalker
可以指定零个或多个选项来配置
。 选项是
- StackWalker
枚举的常量。 常量如下:
- StackWalker.Option
如果指定了 RETAIN_CLASS_REFERENCE 选项,则
返回的栈帧将包含声明由该栈帧表示的方法的类的
- StackWalker
对象的引用。 如果要获取
- Class
对象的方法调用者的引用,也需要指定此选项。 默认情况下,此选项不存在。
- Class
默认情况下,实现特定的和反射栈帧不包括在
类返回的栈帧中。 使用 SHOW_HIDDEN_FRAMES 选项来包括所有隐藏的栈帧。
- StackWalker
如果指定了 SHOW_REFLECT_FRAMES 选项,则
类返回的栈帧流并包含反射栈帧。 使用此选项可能仍然隐藏实现特定的栈帧,可以使用 SHOW_HIDDEN_FRAMES 选项显示。
- StackWalker
在 JDK 9 之前,
类的实例被用来表示栈帧。 JDK 9 中的 Stack-Walker API 使用
- StackTraceElement
接口的实例来表示栈帧。
- StackWalker.StackFrame
Tips
接口没有具体的实现类,可以直接使用。 JDK 中的 Stack-Walking API 在检索栈帧时为你提供了接口的实例。
- StackWalker.StackFrame
接口包含以下方法,其中大部分与
- StackWalker.StackFrame
类中的方法相同:
- StackTraceElement
- int getByteCodeIndex() String getClassName() Class < ?>getDeclaringClass() String getFileName() int getLineNumber() String getMethodName() boolean isNativeMethod() StackTraceElement toStackTraceElement()
在类文件中,使用为 method_info 的结构描述每个方法。 method_info 结构包含一个保存名为 Code 的可变长度属性的属性表。 Code 属性包含一个 code 的数组,它保存该方法的字节码指令。
方法返回到包含由此栈帧表示的执行点的方法的 Code 属性中的代码数组的索引。 它为本地方法返回 - 1。 有关代码数组和代码属性的更多信息,请参阅 "Java 虚拟规范" 第 4.7.3 节,网址为 https://docs.oracle.com/javase/specs/jvms/se8/html/。
- getByteCodeIndex()
如何使用方法的代码数组? 作为应用程序开发人员,不会在方法中使用字节码索引作为执行点。 JDK 确实支持使用内部 API 读取类文件及其所有属性。 可以使用位于 JDK_HOME\bin 目录中的 javap 工具查看方法中每条指令的字节码索引。 需要使用
选项与 javap 打印方法的代码数组。 以下命令显示
- -c
类中所有方法的代码数组:
- LegacyStackWalk
- C: \Java9Revealed > javap - c com.jdojo.stackwalker\build\classes\com\jdojo\stackwalker\LegacyStackWalk.class
输出结果为:
- Compiled from "LegacyStackWalk.java"public class com.jdojo.stackwalker.LegacyStackWalk {
- public com.jdojo.stackwalker.LegacyStackWalk();
- Code: 0 : aload_0 1 : invokespecial#1 // Method java/lang/Object."<init>":()V
- 4 : return public static void main(java.lang.String[]);
- Code: 0 : invokestatic#2 // Method m1:()V
- 3 : return public static void m1();
- Code: 0 : invokestatic#3 // Method m2:()V
- 3 : return public static void m2();
- Code: 0 : getstatic#4 // Field java/lang/System.out:Ljava/io/PrintStream;
- 3 : ldc#5 // String \nWithout using reflection:
- 5 : invokevirtual#6 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
- 8 : invokestatic#7 // Method m3:()V
- ...32 : anewarray#13 // class java/lang/Object
- 35 : invokevirtual#14 // Method java/lang/reflect/Method.invoke:(Ljava/lang/Object;[Ljava/lang/Object;)Ljava/lang/Object;
- ...public static void m3();
- Code: 0 : invokestatic#20 // Method java/lang/Thread.currentThread:()Ljava/lang/Thread;
- 3 : invokevirtual#21 // Method java/lang/Thread.getStackTrace:()[Ljava/lang/StackTraceElement;
- ...
- }
当在方法
中获取调用栈的快照时,
- m3()
方法调用
- m2()
两次。 对于第一次调用,字节码索引为 8,第二次为 35。
- m3()
方法返回声明由栈帧表示的方法的类的
- getDeclaringClass()
对象的引用。 如果该
- Class
没有配置 RETAIN_CLASS_REFERENCE 选项,它会抛出
- StackWalker
异常。
- UnsupportedOperationException
方法返回表示相堆栈帧的
- toStackTraceElement()
类的实例。 如果要使用 JDK 9 API 来获取
- StackTraceElement
,但是继续使用使用
- StackWalker.StackFrame
类的旧代码来分析栈帧,这种方法非常方便。
- StackTraceElement
类包含返回
- StackWalker
实例的静态工厂方法:
- StackWalker
- StackWalker getInstance() StackWalker getInstance(StackWalker.Option option) StackWalker getInstance(Set < StackWalker.Option > options) StackWalker getInstance(Set < StackWalker.Option > options, int estimateDepth)
可以使用不同版本的
方法来配置
- getInstance()
。 默认配置是排除所有隐藏的栈帧,不保留类引用。 允许指定
- StackWalker
的版本使用这些选项进行配置。
- StackWalker.Option
参数是一个提示,指示
- estimateDepth
预计将遍历的栈帧的评估数,因此可能会优化内部缓冲区的大小。
- StackWalker
以下代码片段创建了具有不同配置的
类的四个实例:
- StackWalker
- import java.util.Set;
- import static java.lang.StackWalker.Option. * ;...
- // Get a StackWalker with a default configuration
- StackWalker sw1 = StackWalker.getInstance();
- // Get a StackWalker that shows reflection frames
- StackWalker sw2 = StackWalker.getInstance(SHOW_REFLECT_FRAMES);
- // Get a StackWalker that shows all hidden frames
- StackWalker sw3 = StackWalker.getInstance(SHOW_HIDDEN_FRAMES);
- // Get a StackWalker that shows reflection frames and retains class references
- StackWalker sw4 = StackWalker.getInstance(Set.of(SHOW_REFLECT_FRAMES, RETAIN_CLASS_REFERENCE));
Tips
是线程安全且可重用的。 多个线程可以使用相同的实例遍历自己的栈。
- StackWalker
现在是遍历线程的栈帧的时候了。
类包含两个方法,可以遍历当前线程的栈:
- StackWalker
- void forEach(Consumer < ?super StackWalker.StackFrame > action) < T > T walk(Function < ?super Stream < StackWalker.StackFrame > , ?extends T >
- function)
如果需要遍历整个栈,使用
方法。 指定的
- forEach()
将从栈中提供一个栈帧,从最上面的栈帧开始。 以下代码段打印了
- Consumer
返回的每个栈帧的详细信息:
- StackWalker
- // Prints the details of all stack frames of the current thread
- StackWalker.getInstance().forEach(System.out: :println);
如果要定制栈遍历,例如使用过滤器和映射,使用
方法。
- walk()
方法接受一个
- walk()
,它接受一个
- Function
作为参数,并可以返回任何类型的对象。
- Stream <StackWalker.StackFrame>
将创建栈帧流并将其传递给 function。 当功能完成时,
- StackWalker
将关闭流。 传递给
- StackWalker
方法的流只能遍历一次。 第二次尝试遍历流时会抛出
- walk()
异常。
- IllegalStateException
以下代码片段使用
方法遍历整个栈,打印每个栈帧的详细信息。 这段代码与前面的代码片段使用
- walk()
方法相同。
- forEach()
- // Prints the details of all stack frames of the current thread
- StackWalker.getInstance().walk(s - >{
- s.forEach(System.out: :println);
- return null;
- });
Tips
r 的
- StackWalke
方法用于一次处理一个栈帧,而
- forEach()
方法用于处理将整个栈为帧流。 可以使用
- walk()
方法来模拟
- walk()
方法的功能,但反之亦然。
- forEach()
可能会想知道为什么
方法不返回栈帧流而是将流传递给函数。 没有从方法返回堆栈帧流是有意为之的。 流的元素被懒加载的方式评估。 一旦创建了栈帧流,JVM 就可以自由地重新组织栈,并且没有确定的方法来检测栈已经改变,仍然保留对其流的引用。 这就是创建和关闭栈帧流由
- walk()
类控制的原因。
- StackWalker
由于 Streams API 是广泛的,所以使用
方法。 以下代码片段获取列表中当前线程的栈帧的快照。
- walk()
- import java.lang.StackWalker.StackFrame;
- import java.util.List;
- import static java.util.stream.Collectors.toList;...List < StackFrame > frames = StackWalker.getInstance().walk(s - >s.collect(toList()));
以下代码段收集列表中当前线程的所有栈帧的字符串形式,不包括表示以 m2 开头的方法的栈帧:
- mport java.util.List;
- import static java.util.stream.Collectors.toList;...List < String > list = StackWalker.getInstance().walk(s - >s.filter(f - >!f.getMethodName().startsWith("m2")).map(f - >f.toString()).collect(toList()));
以下代码片段收集列表中当前线程的所有栈帧的字符串形式,不包括声明类名称以 Test 结尾的方法的框架:
- import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
- import java.util.List;
- import static java.util.stream.Collectors.toList;...List < String > list = StackWalker.getInstance(RETAIN_CLASS_REFERENCE).walk(s - >s.filter(f - >!f.getDeclaringClass().getName().endsWith("Test")).map(f - >f.toString()).collect(toList()));
以下代码段以字符串的形式收集整个栈信息,将每个栈帧与平台特定的行分隔符分隔开:
- import static java.util.stream.Collectors.joining;...String stackStr = StackWalker.getInstance() $.walk(s - >s.map(f - >f.toString()).collect(joining(System.getProperty("line.separator"))));
下面包含一个完整的程序,用于展示
类及其
- StackWalker
方法的使用。 它的
- walk()
方法调用
- main()
方法两次,每次通过
- m1()
的一组不同的选项。
- StackWalker
方法使用反射来调用
- m2()
方法,它打印堆栈帧细节信息。 第一次,反射栈帧是隐藏的,类引用不可用。
- m3()
- // StackWalking.java
- package com.jdojo.stackwalker;
- import java.lang.StackWalker.Option;
- import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
- import static java.lang.StackWalker.Option.SHOW_REFLECT_FRAMES;
- import java.lang.StackWalker.StackFrame;
- import java.lang.reflect.InvocationTargetException;
- import java.util.Set;
- import java.util.stream.Stream;
- public class StackWalking {
- public static void main(String[] args) {
- m1(Set.of());
- System.out.println();
- // Retain class references and show reflection frames
- m1(Set.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES));
- }
- public static void m1(Set < Option > options) {
- m2(options);
- }
- public static void m2(Set < Option > options) {
- // Call m3() using reflection
- try {
- System.out.println("Using StackWalker Options: " + options);
- StackWalking.class.getMethod("m3", Set.class).invoke(null, options);
- } catch(NoSuchMethodException | InvocationTargetException | IllegalAccessException | SecurityException e) {
- e.printStackTrace();
- }
- }
- public static void m3(Set < Option > options) {
- // Prints the call stack details
- StackWalker.getInstance(options).walk(StackWalking: :processStack);
- }
- public static Void processStack(Stream < StackFrame > stack) {
- stack.forEach(frame - >{
- int bci = frame.getByteCodeIndex();
- String className = frame.getClassName();
- Class < ?>classRef = null;
- try {
- classRef = frame.getDeclaringClass();
- } catch(UnsupportedOperationException e) {
- // No action to take
- }
- String fileName = frame.getFileName();
- int lineNumber = frame.getLineNumber();
- String methodName = frame.getMethodName();
- boolean isNative = frame.isNativeMethod();
- StackTraceElement sfe = frame.toStackTraceElement();
- System.out.printf("Native Method=%b", isNative);
- System.out.printf(", Byte Code Index=%d", bci);
- System.out.printf(", Module Name=%s", sfe.getModuleName());
- System.out.printf(", Module Version=%s", sfe.getModuleVersion());
- System.out.printf(", Class Name=%s", className);
- System.out.printf(", Class Reference=%s", classRef);
- System.out.printf(", File Name=%s", fileName);
- System.out.printf(", Line Number=%d", lineNumber);
- System.out.printf(", Method Name=%s.%n", methodName);
- });
- return null;
- }
- }
输出的结果为:
- Using StackWalker Options: [] Native Method = false,
- Byte Code Index = 9,
- Module Name = null,
- Module Version = null,
- Class Name = com.jdojo.stackwalker.StackWalking,
- Class Reference = null,
- FileName = StackWalking.java,
- Line Number = 44,
- Method Name = m3.Native Method = false,
- Byte Code Index = 37,
- Module Name = null,
- Module Version = null,
- Class Name = com.jdojo.stackwalker.StackWalking,
- Class Reference = null,
- File Name = StackWalking.java,
- Line Number = 32,
- Method Name = m2.Native Method = false,
- Byte Code Index = 1,
- Module Name = null,
- Module Version = null,
- Class Name = com.jdojo.stackwalker.StackWalking,
- Class Reference = null,
- File Name = StackWalking.java,
- Line Number = 23,
- Method Name = m1.Native Method = false,
- Byte Code Index = 3,
- Module Name = null,
- Module Version = null,
- Class Name = com.jdojo.stackwalker.StackWalking,
- Class Reference = null,
- File Name = StackWalking.java,
- Line Number = 14,
- Method Name = main.Using StackWalker Options: [SHOW_REFLECT_FRAMES, RETAIN_CLASS_REFERENCE] Native Method = false,
- Byte Code Index = 9,
- Module Name = null,
- Module Version = null,
- Class Name = com.jdojo.stackwalker.StackWalking,
- Class Reference = class com.jdojo.stackwalker.StackWalking,
- File Name = StackWalking.java,
- Line Number = 44,
- Method Name = m3.Native Method = true,
- Byte Code Index = -1,
- Module Name = java.base,
- Module Version = 9 - ea,
- Class Name = jdk.internal.reflect.NativeMethodAccessorImpl,
- Class Reference = class jdk.internal.reflect.NativeMethodAccessorImpl,
- File Name = NativeMethodAccessorImpl.java,
- Line Number = -2,
- Method Name = invoke0.Native Method = false,
- Byte Code Index = 100,
- Module Name = java.base,
- Module Version = 9 - ea,
- Class Name = jdk.internal.reflect.NativeMethodAccessorImpl,
- Class Reference = class jdk.internal.reflect.NativeMethodAccessorImpl,
- File Name = NativeMethodAccessorImpl.java,
- Line Number = 62,
- Method Name = invoke.Native Method = false,
- Byte Code Index = 6,
- Module Name = java.base,
- Module Version = 9 - ea,
- Class Name = jdk.internal.reflect.DelegatingMethodAccessorImpl,
- Class Reference = class jdk.internal.reflect.DelegatingMethodAccessorImpl,
- File Name = DelegatingMethodAccessorImpl.java,
- Line Number = 43,
- Method Name = invoke.Native Method = false,
- Byte Code Index = 59,
- Module Name = java.base,
- Module Version = 9 - ea,
- Class Name = java.lang.reflect.Method,
- Class Reference = class java.lang.reflect.Method,
- File Name = Method.java,
- Line Number = 538,
- Method Name = invoke.Native Method = false,
- Byte Code Index = 37,
- Module Name = null,
- Module Version = null,
- Class Name = com.jdojo.stackwalker.StackWalking,
- Class Reference = class com.jdojo.stackwalker.StackWalking,
- File Name = StackWalking.java,
- Line Number = 32,
- Method Name = m2.Native Method = false,
- Byte Code Index = 1,
- Module Name = null,
- Module Version = null,
- Class Name = com.jdojo.stackwalker.StackWalking,
- Class Reference = class com.jdojo.stackwalker.StackWalking,
- File Name = StackWalking.java,
- Line Number = 23,
- Method Name = m1.Native Method = false,
- Byte Code Index = 21,
- Module Name = null,
- Module Version = null,
- Class Name = com.jdojo.stackwalker.StackWalking,
- Class Reference = class com.jdojo.stackwalker.StackWalking,
- File Name = StackWalking.java,
- Line Number = 19,
- Method Name = main.
在 JDK 9 之前,开发人员依靠以下方法来获取调用者的调用:
类的
- SecurityManager
方法,由于该方法受到保护,因此需要进行子类化。
- getClassContext()
类的
- sun.reflect.Reflection
方法,它是一个 JDK 内部类。
- getCallerClass()
JDK 9 通过在
类中添加一个
- StackWalker
的方法,使得获取调用者类引用变得容易。 方法的返回类型是
- getCallerClass()
。 如果
- Class<?>
未配置 R
- StackWalker
选项,则调用此方法将抛出
- ETAIN_CLASS_REFERENCE
异常。 如果栈中没有调用者栈帧,则调用此方法会引发
- UnsupportedOperationException
,例如,运行
- IllegalStateException
方法调用此方法的类。
- main()
那么,哪个类是调用类? 在 Java 中,方法和构造函数可调用。 以下讨论使用方法,但是它也适用于构造函数。 假设在 S 的方法中调用
方法,该方法从 T 的方法调用。另外假设 T 的方法在名为 C 的类中。在这种情况下,C 类是调用者类。
- getCallerClass()
Tips
类的
- StackWalker
方法在查找调用者类时会过滤所有隐藏和反射栈帧,而不管用于获取
- getCallerClass()
实例的选项如何。
- StackWalker
下面包含一个完整的程序来显示如何获取调用者的类。 它的
方法调用
- main()
方法,m1 调用
- m1()
方法,m2 调用
- m2()
方法。
- m3()
方法获取
- m3()
类的实例并获取调用者类。 请注意,
- StackWalker
方法使用反射来调用
- m2()
方法。 最后,
- m3()
方法尝试获取调用者类。 当运行
- main()
类时,
- CallerClassTest
方法由 JVM 调用,栈上不会有调用者栈帧。 这将抛出一个
- main()
异常。
- IllegalStateException
- // CallerClassTest.java
- package com.jdojo.stackwalker;
- import java.lang.StackWalker.Option;
- import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
- import static java.lang.StackWalker.Option.SHOW_REFLECT_FRAMES;
- import java.lang.reflect.InvocationTargetException;
- import java.util.Set;
- public class CallerClassTest {
- public static void main(String[] args) {
- /* Will not be able to get caller class because because the RETAIN_CLASS_REFERENCE
- option is not specified.
- */
- m1(Set.of());
- // Will print the caller class
- m1(Set.of(RETAIN_CLASS_REFERENCE, SHOW_REFLECT_FRAMES));
- try {
- /* The following statement will throw an IllegalStateException if this class is run
- because there will be no caller class; JVM will call this method. However,
- if the main() method is called in code, no exception will be thrown.
- */
- Class < ?>cls = StackWalker.getInstance(RETAIN_CLASS_REFERENCE).getCallerClass();
- System.out.println("In main method, Caller Class: " + cls.getName());
- } catch(IllegalCallerException e) {
- System.out.println("In main method, Exception: " + e.getMessage());
- }
- }
- public static void m1(Set < Option > options) {
- m2(options);
- }
- public static void m2(Set < Option > options) {
- // Call m3() using reflection
- try {
- CallerClassTest.class.getMethod("m3", Set.class).invoke(null, options);
- } catch(NoSuchMethodException | InvocationTargetException | IllegalAccessException | SecurityException e) {
- e.printStackTrace();
- }
- }
- public static void m3(Set < Option > options) {
- try {
- // Print the caller class
- Class < ?>cls = StackWalker.getInstance(options).getCallerClass();
- System.out.println("Caller Class: " + cls.getName());
- } catch(UnsupportedOperationException e) {
- System.out.println("Inside m3(): " + e.getMessage());
- }
- }
- }
输出结果为:
- Inside m3() : This stack walker does not have RETAIN_CLASS_REFERENCE access Caller Class: com.jdojo.stackwalker.CallerClassTest In main method,
- Exception: no caller frame
在前面的例子中,收集栈帧的方法是从同一个类的另一个方法中调用的。 我们从另一个类的方法中调用这个方法来看到一个不同的结果。 下面显示了
的类的代码。
- CallerClassTest2
- // CallerClassTest2.java
- package com.jdojo.stackwalker;
- import java.lang.StackWalker.Option;
- import java.util.Set;
- import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
- public class CallerClassTest2 {
- public static void main(String[] args) {
- Set < Option > options = Set.of(RETAIN_CLASS_REFERENCE);
- CallerClassTest.m1(options);
- CallerClassTest.m2(options);
- CallerClassTest.m3(options);
- System.out.println("\nCalling the main() method:");
- CallerClassTest.main(null);
- System.out.println("\nUsing an anonymous class:");
- new Object() {
- {
- CallerClassTest.m3(options);
- }
- };
- System.out.println("\nUsing a lambda expression:");
- new Thread(() - >CallerClassTest.m3(options)).start();
- }
- }
输出结果为:
- Caller Class: com.jdojo.stackwalker.CallerClassTest Caller Class: com.jdojo.stackwalker.CallerClassTest Caller Class: com.jdojo.stackwalker.CallerClassTest2 Calling the main() method: Inside m3() : This stack walker does not have RETAIN_CLASS_REFERENCE access Caller Class: com.jdojo.stackwalker.CallerClassTest In main method,
- Caller Class: com.jdojo.stackwalker.CallerClassTest2 Using an anonymous class: Caller Class: com.jdojo.stackwalker.CallerClassTest2$1 Using a lambda expression: Caller Class: com.jdojo.stackwalker.CallerClassTest2
类的
- CallerClassTest2
方法调用
- main()
类的四个方法。 当
- CallerClassTest
从
- CallerClassTest.m3()
类直接调用时,调用者类是
- CallerClassTest2
。 当从
- CallerClassTest2
类调用
- CallerClassTest2
方法时,有一个调用者栈帧,调用者类是
- CallerClassTest.main()
类。 当运行
- CallerClassTest2
类时,将其与上一个示例的输出进行比较。 那时,
- CallerClassTest
方法是从 JVM 调用的,不能在
- CallerClassTest.main()
方法中获得一个调用者类,因为没有调用者栈帧。 最后,
- CallerClassTest.main()
方法从匿名类和 lambda 表达式调用。 匿名类被报告为调用者类。 在 lambda 表达式的情况下,它的闭合类被报告为调用者类。
- CallerClassTest.m3()
当存在 Java 安全管理器并且使用 RETAIN_CLASS_REFERENCE 选项配置
时,将执行权限检查,以确保代码库被授予
- StackWalker
的
- retainClassReference
值。 如果未授予权限,则抛出
- java.lang.StackFramePermission
异常。 在创建
- SecurityException
r 实例时执行权限检查,而不是在执行栈遍历时。
- StackWalke
下包含
类的代码。 它的
- StackWalkerPermissionCheck
方法使用 RETAIN_CLASS_REFERENCE 选项创建
- printStackFrames()
实例。 假设没有安全管理器,
- StackWalker
方法调用此方法,它打印堆栈跟踪没有任何问题。 安装安全管理器以后,再次调用
- main()
方法。 这一次,抛出一个
- printStackFrames()
异常,这在输出中显示。
- SecurityException
- // StackWalkerPermissionCheck.java
- package com.jdojo.stackwalker;
- import static java.lang.StackWalker.Option.RETAIN_CLASS_REFERENCE;
- public class StackWalkerPermissionCheck {
- public static void main(String[] args) {
- System.out.println("Before installing security manager:");
- printStackFrames();
- SecurityManager sm = System.getSecurityManager();
- if (sm == null) {
- sm = new SecurityManager();
- System.setSecurityManager(sm);
- }
- System.out.println("\nAfter installing security manager:");
- printStackFrames();
- }
- public static void printStackFrames() {
- try {
- StackWalker.getInstance(RETAIN_CLASS_REFERENCE).forEach(System.out: :println);
- } catch(SecurityException e) {
- System.out.println("Could not create a " + "StackWalker. Error: " + e.getMessage());
- }
- }
- }
输出结果为:
- Before installing security manager: com.jdojo.stackwalker / com.jdojo.stackwalker.StackWalkerPermissionCheck.printStackFrames(StackWalkerPermissionCheck.java: 24) com.jdojo.stackwalker / com.jdojo.stackwalker.StackWalkerPermissionCheck.main(StackWalkerPermissionCheck.java: 9) After installing security manager: Could not create a StackWalker.Error: access denied("java.lang.StackFramePermission""retainClassReference")
下面显示了如何使用 RETAIN_CLASS_REFERENCE 选项授予创建
所需的权限。 授予所有代码库的权限,需要将此权限块添加到位于机器上的 JAVA_HOME\conf\security 目录中的 java.policy 文件的末尾。
- StackWalker
- grant {
- permission java.lang.StackFramePermission "retainClassReference";
- };
当授予权限以后再运行上面的类时,应该会收到以下输出:
- Before installing security manager: com.jdojo.stackwalker / com.jdojo.stackwalker.StackWalkerPermissionCheck.printStackFrames(StackWalkerPermissionCheck.java: 24) com.jdojo.stackwalker / com.jdojo.stackwalker.StackWalkerPermissionCheck.main(StackWalkerPermissionCheck.java: 9) After installing security manager: com.jdojo.stackwalker / com.jdojo.stackwalker.StackWalkerPermissionCheck.printStackFrames(StackWalkerPermissionCheck.java: 24) com.jdojo.stackwalker / com.jdojo.stackwalker.StackWalkerPermissionCheck.main(StackWalkerPermissionCheck.java: 18)
JVM 中的每个线程都有一个私有的 JVM 栈,它在创建线程的同时创建。 栈保存栈帧。 JVM 栈上的一个栈帧表示给定线程中的 Java 方法调用。 每次调用一个方法时,都会创建一个新的栈帧并将其推送到栈的顶部。 当方法调用完成时,框架被销毁(从堆栈中弹出)。 在给定的线程中,任何点只有一个栈帧是活动的。 活动栈帧被称为当前栈帧,其方法称为当前方法。 定义当前方法的类称为当前类。
在 JDK 9 之前,可以使用以下类遍历线程栈中的所有栈帧:
,
- Throwable
和
- hread
。
- StackTraceElement
类的实例表示栈帧。
- StackTraceElement
类的
- Throwable
方法返回包含当前线程栈帧的
- getStrackTrace()
。
- StackTraceElement []
类的
- Thread
方法返回包含线程栈帧的
- getStrackTrace()
。 数组的第一个元素是栈中的顶层栈帧,表示序列中最后一个方法调用。 一些 JVM 的实现可能会在返回的数组中省略一些栈帧。
- StackTraceElement []
JDK 9 使栈遍历变得容易。 它在 java.lang 包中引入了一个
的新类。 可以使用
- StackWalker
的静态工厂方法获取
- getInstance()
的实例。 可以使用
- StackWalker
的枚举中定义的常量来表示的选项来配置
- StackWalker.Option
。
- StackWalker
的嵌套接口的实例表示栈帧。
- StackWalker.StackFrame
类与
- StackWalker
实例配合使用。 该接口定义了
- StackWalker.StackFrame
的方法,可用于从
- toStackTraceElement()
获取
- StackWalker.StackFrame
类的实例。
- StackTraceElement
可以使用
实例的
- StackWalker
和
- forEach()
方法遍历当前线程的栈帧。
- walk()
实例的
- StackWalker
方法返回调用者类引用。 如果想要代表栈帧的类的引用和调用者类的引用,则必须使用 RETAIN_CLASS_REFERENCE 配置
- getCallerClass()
实例。 默认情况下,所有反射栈帧和实现特定的栈帧都不会被
- StackWalker
记录。 如果希望这些框架包含在栈遍历中,请使用 SHOW_REFLECT_FRAMES 和 SHOW_HIDDEN_FRAMES 选项来配置
- StackWalker
。 使用 SHOW_HIDDEN_FRAMES 选项也包括反栈帧。
- StackWalker
当存在 Java 安全管理器并且使用 RETAIN_CLASS_REFERENCE 选项配置
时,将执行权限检查,以确保代码库被授予
- StackWalker
的
- retainClassReference
值。 如果未授予权限,则抛出
- java.lang.StackFramePermission
异常。 在创建
- SecurityException
实例时执行权限检查,而不是执行栈遍历时。
- StackWalker
来源: http://www.cnblogs.com/IcanFixIt/p/7238835.html