我今天一直在编写一个递归函数,递归深度取决于输入长度。
我想知道从纯粹的兴趣的角度来看,是否有某种方法可以监视,可能是在某些 JVM 日志或其他地方,在特定程序执行期间的最大调用堆栈深度是多少?
经过一番思考,我可以想象一种分析方法来大致计算这一点,但这将非常耗时,并且需要对 JVM 内部和字节码有相当好的了解。
JVM允许配置堆栈大小内存的限制,但我从未见过有关如何获得实际达到的限制,而不是内存大小单位,而是分配的堆栈帧数。
可以很容易地制作JVMTI代理,它将跟踪MethodEntry/MethodExit事件,并相应地增加或减少堆栈深度计数器。下面是此类代理的示例。当程序结束时,它将打印记录的最大 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 = %dn", max_depth);
}
编译:
gcc -fPIC -shared -I $JAVA_HOME/include -I $JAVA_HOME/include/linux -o libmaxdepth.so maxdepth.c
跑:
java -agentpath:/path/to/libmaxdepth.so MyProgram
但是,跟踪每个方法的进入和退出非常昂贵。一个不太准确但更有效的替代方案是采样分析器,它定期记录正在运行的线程的堆栈跟踪,例如异步分析器或 Java 飞行记录器。