
Jack是帮Android用的、用Java写的一个把Java源码编译到DEX格式的编译器。而这个编译器自身一般是在Oracle JDK / OpenJDK的HotSpot VM上运行的。这里涉及的参数就是一个HotSpot VM特有的参数。
题主所说的代码是从这个changeset放出来的:
android.googlesource.com/toolchain/jack/+/97458db2738a00b829461967838c2567938c14%5E%21/server/jack-server/etc/jack-admin
android-review.googlesource.com/#/c/203451/
从commit message和review thread可以发现java stack tracejava stack trace,作者加入 -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 ,通常也就够用了。
本文来自电脑杂谈,转载请注明本文网址:
https://www.pc-fly.com/a/shouji/article-141189-1.html
20
是怨那些害人的传统观念