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