如何在不重新启动应用程序的情况下获取 StackOverflowError 的完整堆栈跟踪

How to get the full stacktrace of a StackOverflowError without restarting the application

我目前有一个 运行 Java 应用程序,它有一个错误。我不知道如何完全重现它,直到现在才几周没有发生。当它发生一次时,我可以很容易地一遍又一遍地重现它,直到我重新启动应用程序。由于递归,该错误导致 WhosebugError,我不知道这是怎么发生的。 WhosebugError 的打印堆栈跟踪没有帮助,因为它只包含重复部分,而不包含更有趣的初始部分,因为 JVM 对堆栈跟踪条目有限制。 -XX:MaxJavaStackTraceDepth=... 可用于设置此限制,如 here 所述。问题是我想我必须重新启动我的应用程序才能添加此标志。但如果我这样做,我将无法再重现该错误。有什么解决方案可以让我在不重新启动应用程序的情况下获得完整的堆栈跟踪或设置此标志?

我至少知道两种解法。

  1. 创建HotSpot Serviceability Agent工具在内存中查找MaxJavaStackTraceDepth变量的地址,然后使用OS特定机制更新进程的内存。

  2. 附加一个 JVM TI agent 拦截 WhosebugErrors 并直接从代理打印堆栈跟踪。

这是第一个解决方案的代码(因为它可能更短):

import sun.jvm.hotspot.debugger.Address;
import sun.jvm.hotspot.runtime.VM;
import sun.jvm.hotspot.tools.Tool;

import java.io.IOException;
import java.io.RandomAccessFile;

public class ChangeVMFlag extends Tool {
    private static String pid;

    @Override
    public void run() {
        Address addr = VM.getVM().getCommandLineFlag("MaxJavaStackTraceDepth").getAddress();
        long addrValue = VM.getVM().getDebugger().getAddressValue(addr);

        try (RandomAccessFile raf = new RandomAccessFile("/proc/" + pid + "/mem", "rw")) {
            raf.seek(addrValue);
            raf.writeInt(Integer.reverseBytes(1_000_000));
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        pid = args[0];
        new ChangeVMFlag().execute(new String[]{pid});
    }
}

此工具将目标进程中 MaxJavaStackTraceDepth 的值更改为 100 万。
注意:它使用 Linux-specific /proc API 写入目标进程的内存。其他 OS 有不同的接口。

如何运行

在 JDK 8

java -cp .:$JAVA_HOME/lib/sa-jdi.jar ChangeVMFlag <pid>

在 JDK 9+

java --add-modules=jdk.hotspot.agent \
     --add-exports jdk.hotspot.agent/sun.jvm.hotspot.tools=ALL-UNNAMED \
     --add-exports jdk.hotspot.agent/sun.jvm.hotspot.runtime=ALL-UNNAMED \
     --add-exports jdk.hotspot.agent/sun.jvm.hotspot.debugger=ALL-UNNAMED \
     ChangeVMFlag <pid>