【问题标题】:Java - Save/restore call stackJava - 保存/恢复调用堆栈
【发布时间】:2014-07-10 04:30:43
【问题描述】:

是否可以在执行期间保存调用堆栈的状态,然后将堆栈恢复到该状态,在最顶层的方法调用处重新开始?

【问题讨论】:

  • 你为什么要这样做?
  • 我正在执行一个可以被中断的算法,因为它意识到它所依赖的一些早期信息现在已经过时了。我不想从一开始就重新启动,而是从它使用过时信息的方法重新启动。我只关心这个关于恢复调用堆栈的问题,我并不担心这会如何影响堆。
  • “恢复堆栈状态”是什么意思?一旦你的程序继续执行,之前的堆栈就不再相关了。
  • 通过“在最顶层的方法调用处重新启动”,你想做什么与再次调用该方法有什么不同?
  • 我看过你的其他问题,似乎很多问题都带有一种黑客攻击的底色。我建议您改用更多“标准”方式。您将生成更好的可维护代码。你也会学到一些更持久的东西。您似乎正在学习的黑客技巧并不持久,它们与具体技术有关。一旦技术发生变化,您将只是一个追忆往日美好时光的老嬉皮士:)

标签: java callstack


【解决方案1】:

保存当前调用堆栈非常简单。 Thread 类具有一些面向堆栈跟踪的功能。这很可能是您正在寻找的 -

StackTraceElement[] stack = Thread.currentThread().getStackTrace();

现在您已经保存了堆栈跟踪,以后可以随意使用。我不确定您所说的恢复堆栈跟踪是什么意思。

【讨论】:

  • OP试图做的远不止访问stacktrace,他实际上是在尝试操作堆栈(在堆栈上,不仅有堆栈跟踪信息,还有方法的参数) .而且他不仅想阅读这些信息,还想写。
【解决方案2】:

我强烈建议改变你的范式。与其操纵调用堆栈(很可能会破坏您的 JVM),不如考虑以某种方式将“调用堆栈”合并到您的算法中。

您可以使用策略/命令/撤消设计模式并将对象存储到您自己的逻辑堆栈,您可以完全控制该堆栈。

有一个相关的问题Way to go from recursion to iteration 有很多很好的答案。

【讨论】:

  • 感谢您为这个问题带来一些Keep It Simple Stupid 常识
【解决方案3】:

绝对可以查看堆栈跟踪。在编写过多的 shell 脚本并获得每一行代码的响应之后,我已经这样做了。我所做的是创建一个侦听器线程,它是主线程的子线程。

类似这样的:

public class Debugger implements Serializable {
    private static final long serialVersionUID = 1L;

    private Thread parent;
    private String packageName;
    private File outputFile;
    private boolean alive;

    public Debugger(Thread parent, String packageName, File outputFile) {
        this.parent = parent;
        this.packageName = packageName;
        this.outputFile = outputFile;
        alive = true;
    }

    public void run() throws FileNotFoundException {
        PrintStream ps = new PrintStream(outputFile);
        alive = true;
        String lastClassName = "";
        while (parent.isAlive() && alive) {
            for (StackTraceElement ste : parent.getStackTrace()) {
                if (ste.getClassName().startsWith(packageName)) {
                    if (!ste.getClassName().equals(lastClassName)) {
                        ps.println(ste.toString());
                        lastClassName = ste.getClassName();
                    }
                    break;
                }
            }
        }
        ps.close();
    }

    public void kill() {
        alive = false;
    }
}

如果您在要调试的代码的开头创建此对象,它会将调用的包前缀/子字符串中的每个新方法写入您的文件。然后只需在要监视堆栈跟踪的代码处调用 kill() 即可。如果您的主线程/代码运行速度比该线程快,它可能会错过方法,但它很可能会捕获所有内容。

您可以在其中包含时间戳 (new Date()) 以查看您的方法调用也需要多长时间才能返回。

为了得到您想要的,您可以将这些方法调用保存在堆栈跟踪中,并在以后使用反射再次调用这些方法。但是,我确实认为这是一种不好的做法。

【讨论】:

    【解决方案4】:

    您可以使用正常的程序流控制从特定调用堆栈重新启动,而不是直接弄乱调用堆栈。一种方法是例外:

    Result findResult() {
        while (true) {
            Data data = getData();
            try {
                return processData(data);
            } catch (OutOfDateException e) {
                /* fall through */
            }
        }
    }
    
    Result processData(Data data) throws OutOfDateException {
        /* ... */
        processData2(otherData)
        /* ... */
    }
    
    IntermediateResult processData2(OtherData otherData) throws OutOfDateException {
        /* ... */
        if (myDataWasTooOld())
            throw new OutOfDateException();
        /* ... */
    }
    

    在示例中,如果processData2() 引发异常,则使用data 的新值重新创建相同的调用堆栈(从processData() 调用的processData2())。

    【讨论】:

      【解决方案5】:

      我认为您正在寻找基于 Continuations 的东西... Java Flow 是一个旧项目,但我认为它完全符合您的要求 - 使用字节码“魔术”将堆栈帧保存为 Continuation 对象 - 也就是说,一些额外的代码被编织到您的代码中,以允许将堆栈帧保存到对象中。

      http://commons.apache.org/sandbox/commons-javaflow/tutorial.html

      【讨论】:

        猜你喜欢
        • 2019-08-10
        • 1970-01-01
        • 2017-07-10
        • 2016-11-16
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-11-28
        相关资源
        最近更新 更多