MaxJavaStackTraceDepth是干嘛的?
1 个回答
Jack是给Android用的、用Java写的一个把Java源码编译到DEX格式的编译器。而这个编译器自身通常是在Oracle JDK / OpenJDK的HotSpot VM上运行的。这里涉及的参数就是一个HotSpot VM特有的参数。
题主所说的代码是从这个changeset放进去的:
https://android.googlesource.com/toolchain/jack/+/97458db2738a00b829461967838c2c4567938c14%5E%21/server/jack-server/etc/jack-adminhttps://android-review.googlesource.com/#/c/203451/从commit message和review thread可以看到,作者加入 -XX:MaxJavaStackTraceDepth=-1 参数的目的是让JVM在实现 java.lang.Throwable.fillInStackTrace() 时把整个调用栈上所有Java栈帧的信息都记录下来。
HotSpot VM的 -XX:MaxJavaStackTraceDepth 参数的默认值是1024,也就是说 fillInStackTrace() 在记录到最多1024层调用栈帧信息后就会停止爬栈。这是因为爬栈并将栈帧信息转换为Java对象(StackTraceElement)是个比较慢的动作,限制一下最大深度可以把 fillInStackTrace() 的最大开销给限制住。
product(intx, MaxJavaStackTraceDepth, 1024, \
"The maximum number of lines in the stack trace for Java " \
"exceptions (0 means all)") \
我们可以用一个小实验来演示一下:
public class StackTraceDepthDemo {
public static void demo(int depth) {
if (depth > 0) demo(depth - 1);
else throw new RuntimeException("Count the stack trace depth");
}
public static void main(String[] args) {
demo(2047);
}
}
这个例子运行的时候,如果 fillInStackTrace() 能收集整个调用栈上的所有Java栈帧的信息,那么我们应该能看到2049层栈帧(2048层 demo() + 1层 main()),其中最底下的一行应该是 at StackTraceDepthDemo.main() 。
在Mac OS X上用Oracle JDK8u101以默认参数来运行:
$ java StackTraceDepthDemo 2>&1 | grep 'at StackTraceDepthDemo' | wc -l
1024
看,只爬到了1024层栈帧。如果我们按照题主引用的代码去配置参数:
$ java -XX:MaxJavaStackTraceDepth=-1 StackTraceDepthDemo 2>&1 | grep 'at StackTraceDepthDemo' | wc -l
2049
$ java -XX:MaxJavaStackTraceDepth=-1 StackTraceDepthDemo 2>&1 | tail -n1
at StackTraceDepthDemo.main(StackTraceDepthDemo.java:8)
这样就爬到了整个调用栈的所有Java栈帧了。
原理:
其实这算是HotSpot VM的一个bug,不过是bug被当作feature来用了。
前面提到了 MaxJavaStackTraceDepth 参数的声明,它被声明为 intx 类型,这其实就是 intptr_t ,所以是带符号的、指针宽度的整型。所以它当然可以被设置为负数。
$ java -XX:+PrintFlagsFinal -XX:MaxJavaStackTraceDepth=-1 -version | grep MaxJavaStackTraceDepth
intx MaxJavaStackTraceDepth := -1 {product}
java version "1.8.0_101"
Java(TM) SE Runtime Environment (build 1.8.0_101-b13)
Java HotSpot(TM) 64-Bit Server VM (build 25.101-b13, mixed mode)
但按照这个参数原本的设计意图,它应该只允许非负数的值,所以本来它的声明应该用 uintx (就是uintptr_t)而不应该用 intx 。这就是bug的源头所在。
HotSpot VM里, java.lang.Throwable.fillInStackTrace() 的native实现有如下的结构:
void java_lang_Throwable::fill_in_stack_trace(Handle throwable, methodHandle method, TRAPS) {
if (!StackTraceInThrowable) return;
// ...
int max_depth = MaxJavaStackTraceDepth;
JavaThread* thread = (JavaThread*)THREAD;
BacktraceBuilder bt(CHECK);
// ...
int total_count = 0;
for (frame fr = thread->last_frame(); max_depth != total_count;) {
// ...
bt.push(method, bci, CHECK);
total_count++;
}
// ...
}
可以看到,这里会循环遍历整个调用栈上的所有栈帧,直到 max_depth == total_count 。然而 total_count 是从0开始向上数的,除非已经到整数溢出,不然它一直递增上去的话是无论如何也不会跟配置为 -1 的 max_depth 相等,所以就达到了“不设限制,把整个调用栈的所有栈帧都爬了”的目的。
然而如果HotSpot VM把这个bug给修了,把 MaxJavaStackTraceDepth 的声明改为 uintx 类型的话,配置为 -1 就没用了——HotSpot VM在处理命令行参数的时候如果要对 uintx 类型的参数配置负值的话,这个配置的值会直接被忽略。
如果非要爬整个栈,比较好的做法是把 MaxJavaStackTraceDepth 配置为一个很大的正整数,例如说 INT32_MAX ,通常也就够用了。