Java 有没有一种方法可以知道特定程序运行时达到的最大JVM调用堆栈深度?

Java 有没有一种方法可以知道特定程序运行时达到的最大JVM调用堆栈深度?,java,jvm,Java,Jvm,今天我编写了一个递归函数,递归深度取决于输入长度 我想知道,从纯粹的兴趣角度来看,是否有某种方法可以监控(可能在某些JVM日志或其他地方)在特定程序执行期间的最大调用堆栈深度是多少 经过一番思考后,我可以想象一种分析方法来近似计算这个值,但这将非常耗时,并且需要对JVM内部和字节码有很好的了解 JVM允许配置堆栈大小内存的限制,但我从未见过如何获得实际达到的限制,不是内存大小单位,而是分配的堆栈帧数 可以很容易地创建JVMTI代理,该代理将跟踪/事件,并相应地增加或减少堆栈深度计数器。这是一个这

今天我编写了一个递归函数,递归深度取决于输入长度

我想知道,从纯粹的兴趣角度来看,是否有某种方法可以监控(可能在某些JVM日志或其他地方)在特定程序执行期间的最大调用堆栈深度是多少

经过一番思考后,我可以想象一种分析方法来近似计算这个值,但这将非常耗时,并且需要对JVM内部和字节码有很好的了解


JVM允许配置堆栈大小内存的限制,但我从未见过如何获得实际达到的限制,不是内存大小单位,而是分配的堆栈帧数

可以很容易地创建JVMTI代理,该代理将跟踪/事件,并相应地增加或减少堆栈深度计数器。这是一个这样的代理的例子。当程序结束时,它将打印记录的最大Java堆栈深度

#include <jvmti.h>
#include <stdint.h>
#include <stdio.h>

static volatile int max_depth = 0;

static int adjust_stack_depth(jvmtiEnv *jvmti, int delta) {
    intptr_t depth = 0;
    (*jvmti)->GetThreadLocalStorage(jvmti, NULL, (void**)&depth);
    (*jvmti)->SetThreadLocalStorage(jvmti, NULL, (const void*)(depth + delta));
    return (int)depth;
}

void JNICALL MethodEntry(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, jmethodID method) {
    adjust_stack_depth(jvmti, +1);
}

void JNICALL MethodExit(jvmtiEnv *jvmti, JNIEnv* jni, jthread thread, jmethodID method,
                        jboolean was_popped_by_exception, jvalue return_value) {
    int depth = adjust_stack_depth(jvmti, -1);
    if (depth > max_depth) {
        max_depth = depth;  // TODO: replace with atomic CAS to avoid race condition
    }
}

JNIEXPORT jint JNICALL Agent_OnLoad(JavaVM *vm, char *options, void *reserved) {
    jvmtiEnv* jvmti;
    (*vm)->GetEnv(vm, (void**)&jvmti, JVMTI_VERSION_1_0);

    jvmtiCapabilities capabilities = {0};
    capabilities.can_generate_method_entry_events = 1;
    capabilities.can_generate_method_exit_events = 1;
    (*jvmti)->AddCapabilities(jvmti, &capabilities);

    jvmtiEventCallbacks callbacks = {0};
    callbacks.MethodEntry = MethodEntry;
    callbacks.MethodExit = MethodExit;
    (*jvmti)->SetEventCallbacks(jvmti, &callbacks, sizeof(callbacks));

    (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_ENTRY, NULL);
    (*jvmti)->SetEventNotificationMode(jvmti, JVMTI_ENABLE, JVMTI_EVENT_METHOD_EXIT, NULL);

    return 0;
}

JNIEXPORT void JNICALL Agent_OnUnload(JavaVM *vm) {
    printf("Max stack depth = %d\n", max_depth);
}
运行:


然而,跟踪每个方法的入口和出口是非常昂贵的。一个精度较低但效率更高的替代方案是采样探查器,它定期记录运行线程的堆栈跟踪,例如Java Flight Recorder。

简单的答案是。。。不,答案是。。。对可以很容易地使JVMTI代理处理/事件以跟踪当前堆栈深度。一种精度较低但几乎没有开销的替代方法是使用采样探查器,例如,如果您确实对给定程序和输入数据的堆栈深度感兴趣,则可以使用每个线程配置的不同堆栈大小系统地运行程序,直到您发现导致程序失败的堆栈大小。因为您谈论的是堆栈大小内存,所以您似乎不是在追求最大递归次数,而是程序所需的堆栈内存量。坏消息是。当然,您可能想知道,在JVM选项中指定确切数量的堆栈内存会给您带来什么,但对我来说,拥有一个具有堆的自动内存管理功能的JVM,但固定大小的堆栈在需要时无法扩展,这无论如何都是一个时代错误。你应该非常小心递归算法…@Holger
System.out.println(System.identityHashCode(“helloworld”)都会打印相同的数字。但是嵌套调用的数量根本不能帮助您估计所需的堆栈内存。“所以这只是一个毫无意义的数字。”霍尔格,我要的正是分配的堆栈帧数。也许它在现实的生产生活中不是很有用,但它确实提供了一个有趣的洞察事物如何在引擎盖下工作:)@AlexanderArendar实际上,它与“事物如何在引擎盖下工作”相反;您可以获得嵌套方法调用的正式数量,即与Java源代码中编写的调用数量相匹配,从而精确地破坏任何会阻止创建堆栈框架或使调用无法检测的优化。正如这个答案所说,这就是为什么“跟踪每个方法的入口和出口非常昂贵”的原因之一。它不再像平时那样工作了。@apangin,我已经编译并试用过了。因此,当使用空
main
方法的最简单程序进行测试时,我希望看到
Max stack depth=1
输出。但实际输出是
Max stack depth=30
,如果我从
main
添加了一些类实例化和额外的方法调用,结果仍然是一样的。你能告诉我,我在测试这个方面做错了什么吗?@AlexanderArendar这里没有什么错。在启动
main
JVM之前,运行应用程序启动程序代码,加载并验证主类。启动器也是用Java编写的,因此在本例中,代理基本上计算启动器代码的最大深度。
gcc -fPIC -shared -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -o libmaxdepth.so maxdepth.c
java -agentpath:/path/to/libmaxdepth.so MyProgram